如何在 SwiftUI 的视图中调用方法

How to invoke a method in a view in SwiftUI

刚刚开始使用 SwiftUI。

我在 ContentView 中有一个 GoogleMapsView 使用 CLLocationManager 我通过使用 CLLocationManagerDelegate.

扩展它们来捕获 AppDelegateSceneDelegate class 中的事件

如何从 AppDelegateSceneDelegate 调用 GoogleMapsView 中的方法?

在这个例子中,当位置更改事件通过 CLLocationManagerDelegate 发送到 AppDelegate 实例时,我想调用 .animate 方法,但问题确实更笼统。

与其直接从外部调用 View 方法,不如稍微修改一下逻辑,只在某处更改某种 state 并让 View 自行更新。看看这个算法:


经典(也是最糟糕)的方式:

  1. 位置已更改
  2. 在应用委托中调用委托方法(最好重构到其他地方)
  3. 应用程序委托直接在 view 上调用方法(您应该将对该 view 的引用一直传递给应用程序委托)

虽然上面的算法是你原本要找的,但不是最好的方法,我完全不推荐!但它会起作用 ‍♂️


SwiftUI 方式:

  1. 位置已更改
  2. 负责对象中调用的委托方法(可能是单例位置位置管理器实例‍♂️)
  3. 位置管理器在某处更新 State。 (可能是自身内部的 ObservedObject 变量或 EnvironmentObject 等)
  4. 订阅更改的所有视图属性 将通知更改
  5. 所有通知的视图都会自行更新。

这是应该做的。但是实现这一点的方法不止一种,您应该考虑自己的喜好来选择最适合您的方法。

我做了CLLocationManager和MKMapView的实现,和地图差不多,希望对你有帮助:

简短回答:声明一个 @Binding var foo: Any 每次 foo 更改时您都可以在 GoogleMapView 中进行更改,在本例中 foo 是您的位置,因此您每次 foo 更新时都可以调用 animate。

长答案:

首先我像你一样创建了一个符合 UIViewRepresentable 协议的 Mapview,但是添加了一个@Binding 变量,这是我的“trigger”。

MapView:

struct MapView: UIViewRepresentable {
    @Binding var location: CLLocation // Create a @Binding variable that keeps the location where I want to place the view, every time it changes updateUIView will be called
    private let zoomMeters = 400

    func makeUIView(context: UIViewRepresentableContext<MapView>) -> MKMapView {
        let mapView = MKMapView(frame: UIScreen.main.bounds)
        return mapView
    }

    func updateUIView(_ mapView: MKMapView, context: Context) {
        //When location changes, updateUIView is called, so here I move the map:
        let region = MKCoordinateRegion(center: location.coordinate,
                                        latitudinalMeters: CLLocationDistance(exactly: zoomMeters)!,
                                        longitudinalMeters: CLLocationDistance(exactly: zoomMeters)!)
        mapView.setRegion(mapView.regionThatFits(region), animated: true)
    }
}

然后我将我的 MapView 放在我的 ContentView 中,传递一个位置参数,我将在接下来解释:

ContentView:

struct ContentView: View {

    @ObservedObject var viewModel: ContentViewModel

    var body: some View {
        VStack {
            MapView(location: self.$viewModel.location)
        }
    }
}

在我的 ViewModel 中,我使用委托处理位置更改,这里是代码,注释中有更多详细信息:

class ContentViewModel: ObservableObject {
    //location is a Published value, so the view is updated every time location changes
    @Published var location: CLLocation = CLLocation.init()

    //LocationWorker will take care of CLLocationManager...
    let locationWorker: LocationWorker = LocationWorker()

    init() {
        locationWorker.delegate = self
    }

}

extension ContentViewModel: LocationWorkerDelegate {
    func locationChanged(lastLocation: CLLocation?) {
        //Location changed, I change the value of self.location, it is a @Published value so it will refresh the @Binding variable inside MapView and call MapView.updateUIView
        self.location = CLLocation.init(latitude: lastLocation!.coordinate.latitude, longitude: lastLocation!.coordinate.latitude)
    }
}

最后是负责 CLLocationManager() 的 LocationWorker:

class LocationWorker: NSObject, ObservableObject  {

    private let locationManager = CLLocationManager()
    var delegate: LocationWorkerDelegate?

    let objectWillChange = PassthroughSubject<Void, Never>()

    @Published var locationStatus: CLAuthorizationStatus? {
        willSet {
            objectWillChange.send()
        }
    }

    @Published var lastLocation: CLLocation? {
        willSet {
            objectWillChange.send()
        }
    }

    override init() {
        super.init()
        self.locationManager.delegate = self
        //...
    }
}

protocol LocationWorkerDelegate {
    func locationChanged(lastLocation: CLLocation?)
}

extension LocationWorker: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.last else { return }
        self.lastLocation = location
        //When location changes: I use my delegate ->
        if delegate != nil {
            delegate!.locationChanged(lastLocation: lastLocation)
        }
    }
}