Reputation: 1430
In the view described in the following code, I would like to be able to select a marker, no matter if the marker is a Place or an Accommodation. Both Place and Accommodation are defined as classes with different fields. Both of them have latitude, and longitude. But I can only make one of them selectable depending on which one I put on this line
Map(position: $cameraPosition, selection: $selectedAccommodation)
or
Map(position: $cameraPosition, selection: $selectedAccommodation)
import SwiftUI
import MapKit
struct DestinationMapView: View {
var destination: Destination
@State private var cameraPosition: MapCameraPosition = .automatic
@State private var selectedPlace: Place?
@State private var selectedAccommodation: Accommodation?
//@State private var selectedPlace: SelectablePlace?
var body: some View {
Map(position: $cameraPosition, selection: $selectedPlace) {
ForEach(destination.accommodations ?? []) { accommodation in
Group {
if let lat = accommodation.latitude, let lon = accommodation.longitude {
Marker(accommodation.name, systemImage: "bed.double.fill", coordinate: CLLocationCoordinate2D(latitude: lat, longitude: lon))
.tint(.blue)
}
}
.tag(accommodation)
}
ForEach(destination.places ?? []) { place in
Group {
if let lat = place.latitude, let lon = place.longitude {
Marker(place.name, systemImage: place.placeType.systemImage, coordinate: CLLocationCoordinate2D(latitude: lat, longitude: lon))
}
}
.tag(place)
}
}
.sheet(item: $selectedPlace) { selectedPlace in
Text("Selected Place")
.presentationDetents([.medium, .large])
.presentationDragIndicator(.visible)
}
.sheet(item: $selectedAccommodation) { selectedAccommodation in
Text("Selected Accommodation")
.presentationDetents([.medium, .large])
.presentationDragIndicator(.visible)
}
.onAppear {
if let coordinateRegion = destination.coordinateRegion {
cameraPosition = MapCameraPosition.region(coordinateRegion)
}
}
}
}
How can add the capability to select the marker no matter if it's of Place type or Accommodation type?
Upvotes: 2
Views: 274
Reputation: 6380
Starting on iOS 18 now is possible to do that with MapSelection
This allows you to select any feature on the Map (MapFeature
), i.e. places of interest, and also any marker that you tag
with a MapSelection
.
The only requirement is that the MapSelection
has to accept a value conforming to Hashable
, could be any of your own types or could be as simple as an Int
, String
, or UUID
.
Using some of the code for the original post for illustration purposes (not a complete example).
Assuming that your accommodations and places both contain a unique id
and that is a String
, otherwise adjust as needed.
struct DestinationMapView: View {
// ... other code
@State private var mapSelection: MapSelection<String>? // <<<
// ... other code
var body: some View {
Map(position: $cameraPosition, selection: $mapSelection) {
ForEach(destination.accommodations ?? []) { accommodation in
if let lat = accommodation.latitude, let lon = accommodation.longitude {
Marker(accommodation.name,
systemImage: "bed.double.fill",
coordinate: CLLocationCoordinate2D(
latitude: lat,
longitude: lon))
.tint(.blue)
.tag(MapSelection(accommodation.id)) // <<<
}
}
ForEach(destination.places ?? []) { place in
if let lat = place.latitude, let lon = place.longitude {
Marker(place.name,
systemImage: place.placeType.systemImage,
coordinate: CLLocationCoordinate2D(
latitude: lat,
longitude: lon))
.tag(MapSelection(place.id)) // <<<
}
}
}
// other code
}
}
Upvotes: 0
Reputation: 36782
Try this approach using a dedicated Selection
that holds the kind
and the id
of the
selected Accommodation
or Place
. Adjust the code as required.
enum MarkerType { // <--- here
case place
case accommodation
}
struct Selection: Identifiable, Hashable { // <--- here
let id: UUID
let kind: MarkerType
}
struct DestinationMapView: View {
var destination: Destination
@State private var cameraPosition: MapCameraPosition = .automatic
@State private var selectedPlace: Place?
@State private var selectedAccommodation: Accommodation?
@State private var selection: Selection? // <--- here
var body: some View {
Map(position: $cameraPosition, selection: $selection) { // <--- here
ForEach(destination.accommodations ?? []) { accommodation in
Group {
if let lat = accommodation.latitude, let lon = accommodation.longitude {
Marker(accommodation.name, systemImage: "bed.double.fill", coordinate: CLLocationCoordinate2D(latitude: lat, longitude: lon))
.tint(.blue)
}
}
.tag(Selection(id: accommodation.id, kind: .accommodation))
}
ForEach(destination.places ?? []) { place in
Group {
if let lat = place.latitude, let lon = place.longitude {
Marker(place.name, systemImage: place.placeType, coordinate: CLLocationCoordinate2D(latitude: lat, longitude: lon))
}
}
.tag(Selection(id: place.id, kind: .place))
}
}
.sheet(item: $selection) { selection in // <--- here
if selection.kind == .accommodation {
Text("Selected Accommodation: \(getAccomodation(selection)?.name ?? "no name")")
.presentationDetents([.medium, .large])
.presentationDragIndicator(.visible)
} else {
Text("Selected Place: \(getPlace(selection)?.name ?? "no name")")
.presentationDetents([.medium, .large])
.presentationDragIndicator(.visible)
}
}
.onAppear {
if let coordinateRegion = destination.coordinateRegion {
cameraPosition = MapCameraPosition.region(coordinateRegion)
}
}
}
// --- here
func getAccomodation(_ selection: Selection?) -> Accommodation? {
if let select = selection, let acc = destination.accommodations?.first(where: {$0.id == select.id})
{
return acc
}
return nil
}
func getPlace(_ selection: Selection?) -> Place? {
if let select = selection, let place = destination.places?.first(where: {$0.id == select.id})
{
return place
}
return nil
}
}
// --- for testing
struct ContentView: View {
let destination = Destination(accommodations: [ Accommodation(name: "acc-1", latitude: 51.515, longitude: -0.117),
Accommodation(name: "acc-2", latitude: 51.516, longitude: -0.118),
Accommodation(name: "acc-3", latitude: 51.517, longitude: -0.119)],
places: [ Place(name: "place-1", latitude: 51.525, longitude: -0.127),
Place(name: "place-2", latitude: 51.526, longitude: -0.128),
Place(name: "place-3", latitude: 51.527, longitude: -0.129)],
coordinateRegion: MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 51.52, longitude: -0.116), latitudinalMeters: 3500, longitudinalMeters: 3500))
var body: some View {
DestinationMapView(destination: destination)
}
}
struct Place: Identifiable {
let id = UUID()
var name: String
var latitude: Double?
var longitude: Double?
var placeType: String = "house" // <--- for testing
}
struct Accommodation: Identifiable {
let id = UUID()
var name: String
var latitude: Double?
var longitude: Double?
}
struct Destination: Identifiable {
let id = UUID()
var accommodations: [Accommodation]?
var places: [Place]?
var coordinateRegion: MKCoordinateRegion?
}
Upvotes: 3