SwiftUI - Mapkit - 绑定 mapkit 并在注释标注按钮上显示视图

问题描述

我有一个 SwiftUI 应用程序,我在其中显示了一张地图(使用由 UIViewRepresentable 制作的 Mapkit),其中显示了不同的注释。在每个注释上,我都实现了一个标注按钮,我想在按下按钮时完全改变主视图。

我的想法是使用主视图和地图视图的状态/绑定参数。这个想法可行,但我在使用地图视图的方式实现状态/绑定时遇到了一些困难。困难在于我将地图用作包含与地图交互的菜单的第二个视图的参数。我试图改变我使用地图视图的方式,但我没有找到任何解决方案...

我的想法是在 body 之前创建地图:@State var map = MapView(displaySP: $displayStorePage),但我不能这样做(错误:不能在属性初始值设定项中使用实例成员 '$displayStorePage';属性初始值设定项在 'self' 可用之前运行)

非常欢迎任何帮助:)

edit:附加代码显示,我尝试做最轻的版本。

因此,在代码中,目标是从为注释激活的 Mapview 函数切换 Mainview 中的 displayStorePage 点击 func mapView(_ mapView: MKMapView,annotationView view: MKAnnotationView,calloutAccessoryControlTapped control: UIControl)

主视图:

import SwiftUI
import MapKit

struct MainView: View {
    @State var researchStore: String = ""
    @State var displayStorePage: Bool = false
    @State var map = MapView()
    
    var body: some View {
        ZStack{
            if displayStorePage == false {
                vstack{
                  map.frame(height: 400)
                }
            TopMapMenu(map: $map)
            }
            
            if displayStorePage == true {
                Text("This is a win !")
                }
            
            }
        }
    }

MapKit 视图:

import Combine

var storeLocation = [MKAnnotation]()
var newAnnotation = MKPointAnnotation()

let mapView = MKMapView()
var displayRedMarker: Bool = true
var displayStore: Bool = false


struct MapView: UIViewRepresentable {

    class Coordinator: NSObject,MKMapViewDelegate{
        var parent : MapView

        init(_ parent: MapView){
            self.parent = parent
        }
        
        
        func mapView(_ mapView: MKMapView,viewFor annotation: MKAnnotation) -> MKAnnotationView? {
            
            var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "AnnotationView")
            
                annotationView = MKPinAnnotationView(annotation: annotation,reuseIdentifier: "AnnotationView")
            
            let annotationLabel = UILabel(frame: CGRect(x: -25,y: 12,width: 105,height: 30))
            if displayRedMarker {
            annotationLabel.text = annotation.title!!
            } else {
            annotationLabel.text = "Annotation to be hidden"
            }
            
            //Other markers done there ....

            annotationView?.canShowCallout = true
            let btn = UIButton(type: .detaildisclosure)
                        annotationView?.rightCalloutAccessoryView = btn
            return annotationView
        }
        
        // Gestion click Annotation button
        func mapView(_ mapView: MKMapView,calloutAccessoryControlTapped control: UIControl) {
               // Here : Changing the Main View parameter : displayStorePage to true
        }
        
}
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    
    func makeUIView(context: Context) -> some UIView {
        
       
        let region = MKCoordinateRegion(
            center: CLLocationCoordinate2D(latitude: 47.510053,longitude: 6.798374),span: MKCoordinateSpan(latitudeDelta: 0.01,longitudeDelta: 0.01))
        
        mapView.delegate = context.coordinator
        mapView.showsUserLocation = true
        mapView.setRegion(region,animated: false)
        
        mapView.addAnnotation(AddAnnotations())
        
        return mapView
    }
    
    func updateUIView(_ uiView: UIViewType,context: Context) {
        
    }
 
    func AddAnnotations() -> MKAnnotation {
        
        newAnnotation = MKPointAnnotation()
        newAnnotation.title = "Title1"
        newAnnotation.coordinate = CLLocationCoordinate2D(latitude: 47.510053,longitude: 6.798374)
        
        return newAnnotation
    }
    
}

TopMenuMap:

struct TopMapMenu: View {
    
     @Binding var map : MapView
     @State var searching: Bool = false
     @State var redStore: Bool = true
     @State var storeFound : Bool = false
    
    var body: some View {
            
                HStack{
                    vstack(alignment: .leading,spacing: 10){
                        
                        Button(action: {
                            displayRedMarker.toggle()
                         //   map.DeleteMarkers()
                         //   map.AddMarkers()
                            redStore = displayRedMarker
                        }) {
                            HStack {
                            if redStore { Image(systemName: "heart.fill") }
                            else { Image(systemName: "heart") }
                                
                                Text("Restaurant")
                                }
                            Spacer()
                                .background(Color(.white))
                            }
                        // Other Buttons(Pins) are display here
                    }// End vstack Pin stores
                }
    }
}

解决方法

import SwiftUI
import MapKit
class Store: Identifiable,ObservableObject {
    let id: UUID = UUID()
    @Published var title: String?
    @Published var latitude: Double
    @Published var longitude: Double
    @Published var displayStoreImage: Bool
    @Published var displayRedMarker: Bool
    @Published var redStore: Bool
    init(title: String,latitude: Double,longitude: Double,displayStoreImage: Bool,displayRedMarker: Bool,redStore: Bool ) {
        self.title = title
        self.latitude = latitude
        self.longitude = longitude
        self.displayRedMarker = displayRedMarker
        self.displayStoreImage = displayStoreImage
        self.redStore = redStore
        
    }
}
extension Store{
    var coordinate: CLLocationCoordinate2D{
        get{
            return CLLocationCoordinate2D(latitude: self.latitude,longitude: self.longitude)
        }
    }
}

struct MapViewParent: View {
    @State var selectedListing: Store?
    @State var results: [Store] = [Store(title: "Point 1 title",latitude: 38.3,longitude: 123.2,displayStoreImage: true,displayRedMarker: true,redStore: false ),Store(title: "Point 2 title",latitude: 38.4,longitude: 123.1,displayRedMarker: false,redStore: true )
    ]
    var body: some View {
        VStack{
            Button("clear-storepage",action: {
                selectedListing = nil
            })
            if selectedListing != nil{
                TopMapMenu(results: $results,selectedListing: $selectedListing)
            }else{
                MapViewAnnotations(selectedListing: $selectedListing,results: $results)
            }
        }
    }
}
struct MapViewAnnotations: View {
    @State var region: MKCoordinateRegion = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 38.3,longitude: 123.2 ),span: MKCoordinateSpan(latitudeDelta: 0.5,longitudeDelta: 0.5))
    
    @Binding var selectedListing: Store?
    @Binding var results: [Store]
    
    var body: some View {
        Map(coordinateRegion: $region,annotationItems: results,annotationContent: {
            listing in
            MapAnnotation(coordinate: listing.coordinate,content: {
                Button(action: {
                    self.selectedListing = listing as Store?
                    self.region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: listing.coordinate.latitude,longitude: listing.coordinate.longitude ),longitudeDelta: 0.5))
                },label: {
                    if listing.displayStoreImage{
                        if listing.displayRedMarker{
                            Image(systemName: "mappin.circle.fill").foregroundColor(.red).contentShape(Circle())
                        }else{
                            Image(systemName: "mappin").foregroundColor(.green).contentShape(Circle())
                        }
                    }
                    else{
                        Image(systemName: "mappin.circle.fill").foregroundColor(.red).contentShape(Circle())
                    }
                }).buttonStyle(PlainButtonStyle())
            })
        })
        
    }
}

struct TopMapMenu: View {
    @Binding var results: [Store]
    @Binding var selectedListing: Store?
    var body: some View {
        List{
            ForEach(results,id: \.id){store in
                StoreView(store: store,selectedListing: $selectedListing,results: $results)
            }
        }
    }
}
struct StoreView: View {
    @ObservedObject var store: Store
    @Binding var selectedListing: Store?
    @Binding var results: [Store]
    var body: some View{
        HStack{
            VStack(alignment: .leading,spacing: 10){
                Button(action: {
                    store.displayRedMarker.toggle()
                    store.redStore = store.displayRedMarker
                },label: {
                    HStack {
                        if store.redStore {
                            Image(systemName: "heart.fill")
                        }
                        else { Image(systemName: "heart") }
                        
                        Text("Restaurant")
                    }
                    Spacer()
                        .background(Color(.white))
                }
                )}
            MapViewAnnotations(selectedListing: $selectedListing,results: Binding(get: {
                return [store]
            },set: {
                let idx = results.firstIndex(where: {
                    store.id == $0.id
                })
                if idx != nil && $0.first != nil{
                    results[idx!] = $0.first!
                }
            }))
        }
    }
}
struct MapViewParent_Previews: PreviewProvider {
    static var previews: some View {
        MapViewParent()
    }
}
,

好吧,我尝试了很多方法,但对我来说没有任何效果,但是我找到了一个有效的解决方案,但是......它看起来不像是正确的方法......我没想到 TopMapMenu 会主动改变VStack 中的 Mapkit 但显然确实如此。如果它可以帮助某人或如果有人有更好的解释,我会发布新帖子。

struct MainView: View {
    @State var displayStorePage: Bool = false
    @State var map = MapView(displaySP: Binding.constant(false))
    
    var body: some View {   
        ZStack{
         
            if displayStorePage == false {
                VStack{
                    MapView(displaySP: $displayStorePage)
                    Spacer()
                }
                
            TopMapMenu(map: $map)
            }
            
            if displayStorePage == true {
                StorePage()
            }
            
        }
    }

对于 Mapkit:

struct MapView: UIViewRepresentable {
 
 @Binding var displayStorePage: Bool 
    
    init(displaySP: Binding<Bool>) {
        self._displayStorePage = displaySP 
        loadAnnotation()
    }
...