在主 (ContentView) SwiftUI 视图中将 MKMapView 元素作为 UIViewRepresentable 访问 [英] Accessing MKMapView elements as UIViewRepresentable in the main (ContentView) SwiftUI view

查看:19
本文介绍了在主 (ContentView) SwiftUI 视图中将 MKMapView 元素作为 UIViewRepresentable 访问的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

主视图(ContentView)代码为:

struct ContentView:查看{@State private var selected = falsevar主体:一些视图{虚拟堆栈{地图视图().edgesIgnoringSafeArea(.top).frame(高度:选择?600:无).tapAction {withAnimation{ self.chosen.toggle()}}如果选择{提取视图()}}}}

MapView 代码是:

struct MapView : UIViewRepresentable {@State 私有变量 userLocationIsEnabled = falsevar locationManager = CLLocationManager()func makeUIView(context: Context) ->MKMapView {MKMapView(框架:.零)}func updateUIView(_ view: MKMapView, context: Context) {view.showsUserLocation = true...让样本坐标 = [CLLocation(纬度:xx.xxx,经度:xx.xxx),CLLocation(纬度:xx.xxx,经度:xx.xxx),CLLocation(纬度:xx.xxx,经度:xx.xxx)]添加注释(坐标:样本坐标,视图:视图)}}}

我希望能够访问地图视图注释并在另一个视图中定义点击操作.

解决方案

在 SwiftUI DSL 中,您无法访问视图.

相反,您可以组合它们的表示"来创建视图.

一个图钉可以用一个对象来表示 - 操作图钉也会更新地图.

这是我们的 pin 对象:

class MapPin: NSObject, MKAnnotation {让坐标:CLLocationCoordinate2D让标题:字符串?让副标题:字符串?让动作:(() -> Void)?初始化(坐标:CLLocationCoordinate2D,标题:字符串?= 零,副标题:字符串?= 零,动作:(() -> Void)?= 零) {self.coordinate = 坐标self.title = 标题self.subtitle = 副标题self.action = 行动}}

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

(有关 UIViewRepresentable 和协调器的更多信息可以在 WWDC 2019 的精彩演讲中找到 -

I am using SwiftUI to display a map and if user tapped on an annotation, it pops up a detail view in the VStack. I have made the map view and inserted annotations in another SwiftUI file. I also made the detail view.

How can I access the annotations of that map in the main view file to define a .tapaction for them to use it for the detailed view?

I tried defining the view as MKMapView but it is not possible to do it for a UIViewRepresentable inside another SwiftUI view.

The main view (ContentView) code is:

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()
            }
        }
    }
}

The MapView code is:

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)

        }
    }

}

I expect to be able to access map view annotations and define tapaction in another view.

解决方案

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 :)

这篇关于在主 (ContentView) SwiftUI 视图中将 MKMapView 元素作为 UIViewRepresentable 访问的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆