In SwiftUI DSL you don't access views.
Instead, you combine "representations" of them to create views.
A pin can be represented by an object - manipulating the pin will also update the map.
This is our pin object:
class MapPin: NSObject, MKAnnotation {
let coordinate: CLLocationCoordinate2D
let title: String?
let subtitle: String?
let action: (() -> Void)?
init(coordinate: CLLocationCoordinate2D,
title: String? = nil,
subtitle: String? = nil,
action: (() -> Void)? = nil) {
self.coordinate = coordinate
self.title = title
self.subtitle = subtitle
self.action = action
}
}
Here's my Map
, which is not just UIViewRepresentable
, but also makes use of a Coordinator
.
(More about UIViewRepresentable
and coordinators can be found in the excellent WWDC 2019 talk - Integrating SwiftUI)
struct Map : UIViewRepresentable {
class Coordinator: NSObject, MKMapViewDelegate {
@Binding var selectedPin: MapPin?
init(selectedPin: Binding<MapPin?>) {
_selectedPin = selectedPin
}
func mapView(_ mapView: MKMapView,
didSelect view: MKAnnotationView) {
guard let pin = view.annotation as? MapPin else {
return
}
pin.action?()
selectedPin = pin
}
func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
guard (view.annotation as? MapPin) != nil else {
return
}
selectedPin = nil
}
}
@Binding var pins: [MapPin]
@Binding var selectedPin: MapPin?
func makeCoordinator() -> Coordinator {
return Coordinator(selectedPin: $selectedPin)
}
func makeUIView(context: Context) -> MKMapView {
let view = MKMapView(frame: .zero)
view.delegate = context.coordinator
return view
}
func updateUIView(_ uiView: MKMapView, context: Context) {
uiView.removeAnnotations(uiView.annotations)
uiView.addAnnotations(pins)
if let selectedPin = selectedPin {
uiView.selectAnnotation(selectedPin, animated: false)
}
}
}
The idea is:
- The pins are a
@State
on the view containing the map, and are passed down as a binding.
- Each time a pin is added or removed, it will trigger a UI update - all the pins will be removed, then added again (not very efficient, but that's beyond the scope of this answer)
- The
Coordinator
is the map delegate - I can retrieve the touched MapPin
from the delegate methods.
To test it:
struct ContentView: View {
@State var pins: [MapPin] = [
MapPin(coordinate: CLLocationCoordinate2D(latitude: 51.509865,
longitude: -0.118092),
title: "London",
subtitle: "Big Smoke",
action: { print("Hey mate!") } )
]
@State var selectedPin: MapPin?
var body: some View {
NavigationView {
VStack {
Map(pins: $pins, selectedPin: $selectedPin)
.frame(width: 300, height: 300)
if selectedPin != nil {
Text(verbatim: "Welcome to (selectedPin?.title ?? "???")!")
}
}
}
}
}
...and try zooming/tapping the pin on London, UK :)
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…