Le passage des données à partir de UIViewRepresentable fonction de SwiftUI Vue

0

La question

L'utilisateur recherche l'adresse de livraison sur la carte, puis l'adresse est identifiée par le marqueur situé dans le centre de l'écran. Et puis, l'adresse est obtenue par le biais de ce marqueur. Comment afficher une adresse dans l'interface utilisateur ?

struct MapView: UIViewRepresentable {

@Binding var centerCoordinate: CLLocationCoordinate2D

var currentLocation: CLLocationCoordinate2D?
var withAnnotation: MKPointAnnotation?

class Coordinator: NSObject, MKMapViewDelegate {
    
    var parent: MapView
    var addressLabel: String = "222"
    
    init(_ parent: MapView) {
        self.parent = parent
    }
    
    func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
        if !mapView.showsUserLocation {
            parent.centerCoordinate = mapView.centerCoordinate
        }
    }
    
    ...
    
    func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool){
        
        let center = getCenterLocation(for: mapView)
        let geoCoder = CLGeocoder()
        
        geoCoder.reverseGeocodeLocation(center) { [weak self] (placemarks, error) in
            guard let self = self else { return }
            
            if let _ = error {
                //TODO: Show alert informing the user
                print("error")
                return
            }
            
            guard let placemark = placemarks?.first else {
                //TODO: Show alert informing the user
                return
            }
            
            let streetNumber = placemark.subThoroughfare ?? ""
            let streetName = placemark.thoroughfare ?? ""
            
            DispatchQueue.main.async {
                self.addressLabel =  String("\(streetName) | \(streetNumber)")
                print(self.addressLabel)
                
            }
        }
    }
}

func makeCoordinator() -> Coordinator {
    Coordinator(self)
}

func makeUIView(context: Context) -> MKMapView {
    let mapView = MKMapView()
    mapView.delegate = context.coordinator
    mapView.showsUserLocation = false
    return mapView
}

func updateUIView(_ uiView: MKMapView, context: Context) {
    if let currentLocation = self.currentLocation {
        if let annotation = self.withAnnotation {
            uiView.removeAnnotation(annotation)
        }
        uiView.showsUserLocation = true
        let region = MKCoordinateRegion(center: currentLocation, latitudinalMeters: 1000, longitudinalMeters: 1000)
        uiView.setRegion(region, animated: true)
    } else if let annotation = self.withAnnotation {
        uiView.removeAnnotations(uiView.annotations)
        uiView.addAnnotation(annotation)
        }
    }
}

Je suis en train de passer l'adresse de l'INTERFACE utilisateur. Quelle est la façon correcte de faire cela? Dans l'interface, je veux obtenir l'adresse de l'évolution constante de la variable addressLabel

import SwiftUI
import MapKit

fileprivate let locationFetcher = LocationFetcher()

struct LocationView: View {

@State var centerCoordinate = CLLocationCoordinate2D()
@State var currentLocation: CLLocationCoordinate2D?
@State var annotation: MKPointAnnotation?

var body: some View {
    ZStack {
    
        MapView(centerCoordinate: $centerCoordinate, currentLocation: currentLocation, withAnnotation: annotation)
            .edgesIgnoringSafeArea([.leading, .trailing, .bottom])
            .onAppear(perform: {
                locationFetcher.start()
            })
    }
    .overlay(
    
        ZStack {


            Text("\(*MapView(centerCoordinate: $centerCoordinate, currentLocation: currentLocation, withAnnotation: annotation).makeCoordinator().addressLabel OMG????*)")
            
                .offset(y: 44)
        }
    
    )
}

struct LocationView_Previews: PreviewProvider {
    static var previews: some View {
        LocationView()
    }
}

Comment puis-je faire cela ?

Merci d'avance

geolocation properties swift swiftui
2021-11-23 15:26:40
1

La meilleure réponse

1

Ici est une approche. Avoir une source unique de la vérité que les deux UIKit et SwiftUI peut accéder.

@available(iOS 15.0, *)
struct LocationView: View {
    //It is better to have one source of truth
    @StateObject var vm: MapViewModel = MapViewModel()
    
    var body: some View {
        ZStack {
            MapView(vm: vm)
                .edgesIgnoringSafeArea([.leading, .trailing, .bottom])
                .onAppear(perform: {
                    //locationFetcher.start() //No Code provided
                })
        }
        .overlay(
            HStack{
                Spacer()
                Text(vm.addressLabel)
                Spacer()
                //Using offset is subjective since screen sizes change just center it
            }
            
            
        )
        //Sample alert that adapts to what is
        .alert(isPresented: $vm.errorAlert.isPresented, error: vm.errorAlert.error, actions: {
            
            if vm.errorAlert.defaultAction != nil{
                Button("ok", role: .none, action: vm.errorAlert.defaultAction!)
            }
            
            if vm.errorAlert.cancelAction != nil{
                Button("cancel", role: .cancel, action: vm.errorAlert.cancelAction!)
            }
            
            if vm.errorAlert.defaultAction == nil && vm.errorAlert.cancelAction == nil {
                Button("ok", role: .none, action: {})
            }
        })
    }
}
//UIKit and SwiftUI will have access to this ViewModel so all the data can have one souce of truth
class MapViewModel: ObservableObject{
    //All the variables live here
    @Published  var addressLabel: String = "222"
    @Published var centerCoordinate: CLLocationCoordinate2D = CLLocationCoordinate2D()
    
    @Published var currentLocation: CLLocationCoordinate2D? = nil
    @Published var withAnnotation: MKPointAnnotation? = nil
    @Published var annotation: MKPointAnnotation?
    //This tuple variable allows you to have a dynamic alert in the view
    @Published var errorAlert: (isPresented: Bool, error: MapErrors, defaultAction: (() -> Void)?, cancelAction: (() -> Void)?) = (false, MapErrors.unknown, nil, nil)
    //The new alert requires a LocalizedError
    enum MapErrors: LocalizedStringKey, LocalizedError{
        case unknown
        case failedToRetrievePlacemark
        case failedToReverseGeocode
        case randomForTestPurposes
        //Add localizable.strings to you project and add these keys so you get localized messages
        var errorDescription: String?{
            switch self{
                
            case .unknown:
                return "unknown".localizedCapitalized
            case .failedToRetrievePlacemark:
                return "failedToRetrievePlacemark".localizedCapitalized
                
            case .failedToReverseGeocode:
                return "failedToReverseGeocode".localizedCapitalized
                
            case .randomForTestPurposes:
                return "randomForTestPurposes".localizedCapitalized
                
            }
        }
    }
    //Presenting with this will ensure that errors keep from gettting lost by creating a loop until they can be presented
    func presentError(isPresented: Bool, error: MapErrors, defaultAction: (() -> Void)?, cancelAction: (() -> Void)?, count: Int = 1){
        //If there is an alert already showing
        if errorAlert.isPresented{
            //See if the current error has been on screen for 10 seconds
            if count >= 10{
                //If it has dismiss it so the new error can be posted
                errorAlert.isPresented = false
            }
            //Call the method again in 1 second
            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                let newCount = count + 1
                self.presentError(isPresented: isPresented, error: error, defaultAction: defaultAction, cancelAction: cancelAction, count: newCount)
            }
        }else{
            errorAlert = (isPresented, error, defaultAction, cancelAction)
        }
    }
    
}
struct MapView: UIViewRepresentable {
    @ObservedObject var vm: MapViewModel
    
    class Coordinator: NSObject, MKMapViewDelegate {
        var parent: MapView
        
        init(_ parent: MapView) {
            self.parent = parent
        }
        
        func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
            if !mapView.showsUserLocation {
                parent.vm.centerCoordinate = mapView.centerCoordinate
            }
        }
        
        
        func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool){
            getAddress(center: mapView.centerCoordinate)
            //Just to demostrate the error
            //You can remove this whenever
#if DEBUG
            if Bool.random(){
                self.parent.vm.presentError(isPresented: true, error: MapViewModel.MapErrors.randomForTestPurposes, defaultAction: nil, cancelAction: nil)
                
            }
#endif
            
        }
        //Gets the addess from CLGeocoder if available
        func getAddress(center: CLLocationCoordinate2D){
            let geoCoder = CLGeocoder()
            
            geoCoder.reverseGeocodeLocation(CLLocation(latitude: center.latitude, longitude: center.longitude)) { [weak self] (placemarks, error) in
                guard let self = self else { return }
                
                if let _ = error {
                    //TODO: Show alert informing the user
                    print("error")
                    self.parent.vm.presentError(isPresented: true, error: MapViewModel.MapErrors.failedToReverseGeocode, defaultAction: nil, cancelAction: nil)
                    return
                }
                
                guard let placemark = placemarks?.first else {
                    //TODO: Show alert informing the user
                    self.parent.vm.presentError(isPresented: true, error: MapViewModel.MapErrors.failedToRetrievePlacemark, defaultAction: nil, cancelAction: nil)
                    return
                }
                
                let streetNumber = placemark.subThoroughfare ?? ""
                let streetName = placemark.thoroughfare ?? ""
                
                DispatchQueue.main.async {
                    self.parent.vm.addressLabel =  String("\(streetName) | \(streetNumber)")
                    print(self.parent.vm.addressLabel)
                    
                }
            }
        }
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    func makeUIView(context: Context) -> MKMapView {
        let mapView = MKMapView()
        mapView.delegate = context.coordinator
        mapView.showsUserLocation = false
        return mapView
    }
    
    func updateUIView(_ uiView: MKMapView, context: Context) {
        if let currentLocation = vm.currentLocation {
            if let annotation = vm.withAnnotation {
                uiView.removeAnnotation(annotation)
            }
            uiView.showsUserLocation = true
            let region = MKCoordinateRegion(center: currentLocation, latitudinalMeters: 1000, longitudinalMeters: 1000)
            uiView.setRegion(region, animated: true)
        } else if let annotation = vm.withAnnotation {
            uiView.removeAnnotations(uiView.annotations)
            uiView.addAnnotation(annotation)
        }
    }
}
2021-11-23 17:18:56

Dans d'autres langues

Cette page est dans d'autres langues

Русский
..................................................................................................................
Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Türk
..................................................................................................................
Česk
..................................................................................................................
Português
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................