在主(ContentView)SwiftUI 视图中将 MKMapView 元素作为 UIViewRepresentable 访问

Accessing MKMapView elements as UIViewRepresentable in the main (ContentView) SwiftUI view

我正在使用 SwiftUI 显示地图,如果用户点击注释,它会在 VStack 中弹出详细视图。我制作了地图视图并在另一个 SwiftUI 文件中插入了注释。我还制作了详细视图。

如何在主视图文件中访问该地图的注释以定义 .tapaction 供他们将其用于详细视图?

我尝试将视图定义为 MKMapView,但无法为另一个 SwiftUI 视图中的 UIViewRepresentable 执行此操作。

主视图(ContentView)代码为:

struct ContentView: View {
    @State private var chosen = false

    var body: some View {

        VStack {
            MapView()
            .edgesIgnoringSafeArea(.top)
            .frame(height: chosen ? 600:nil)
            .tapAction {
            withAnimation{ self.chosen.toggle()}
            }

    if chosen {
        ExtractedView()
            }
        }
    }
}

MapView代码为:

struct MapView : UIViewRepresentable {
    @State private var userLocationIsEnabled = false
    var locationManager = CLLocationManager()
    func makeUIView(context: Context) -> MKMapView {
    MKMapView(frame: .zero)

    }
    func updateUIView(_ view: MKMapView, context: Context) {

        view.showsUserLocation = true

        .
        .
        .

            let sampleCoordinates = [
                CLLocation(latitude: xx.xxx, longitude: xx.xxx),
                CLLocation(latitude: xx.xxx, longitude: xx.xxx),
                CLLocation(latitude: xx.xxx, longitude: xx.xxx)
                ]
            addAnnotations(coords: sampleCoordinates, view: view)

        }
    }

}

我希望能够访问地图视图注释并在另一个视图中定义 tapaction。

在 SwiftUI DSL 中你不能访问视图。

相反,您可以组合 "representations" 个视图来创建视图。

图钉可以用对象表示 - 操纵图钉也会更新地图。

这是我们的图钉对象:

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
    }

}

这是我的 Map,它不仅 UIViewRepresentable,而且还使用了 Coordinator

(有关 UIViewRepresentable 和协调员的更多信息,请参阅精彩的 WWDC 2019 演讲 - 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)
        }

    }

}

思路是:

  • 图钉是包含地图的视图上的 @State,并作为绑定向下传递。
  • 每次添加或删除 pin 时,都会触发 UI 更新 - 所有的 pin 都将被删除,然后再次添加(效率不高,但这超出了本答案的范围)
  • Coordinator 是地图委托 - 我可以从委托方法中检索触摸的 MapPin

测试一下:

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 ?? "???")!")
                }
            }
        }

    }

}

...然后尝试 zooming/tapping 英国伦敦的图钉 :)