将数据从 UIViewRepresentable 函数传递到 SwiftUI 视图

Passing data from UIViewRepresentable function to SwiftUI View

用户在地图上查找送货地址,然后地址由位于屏幕中间的标记标识。然后通过这个标记获取地址。如何在用户界面中显示地址?

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

我正在尝试将地址传递给 UI。 最正确的方法是什么? 在界面中,我想从一个不断变化的变量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()
    }
}

我该怎么做?

提前致谢

这是一种方法。拥有 UIKit 和 SwiftUI 都可以访问的单一事实来源。

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