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

如何在SwiftUI的视图中调用方法,swiftui,google-maps-sdk-ios,Swiftui,Google Maps Sdk Ios,刚刚开始使用SwiftUI 我在ContentView中有一个 使用CLLocationManagerI捕获AppDelegate或SceneDelegate类中的事件,方法是使用CLLocationManagerDelegate扩展它们 如何从AppDelegate或SceneDelegate调用GoogleMapsView中的方法 在本例中,当位置更改事件通过CLLocationManagerDelegate发送到AppDelegate实例时,我想调用.animate方法,但问题实际上更一般

刚刚开始使用SwiftUI

我在
ContentView中有一个
使用
CLLocationManager
I捕获
AppDelegate
SceneDelegate
类中的事件,方法是使用
CLLocationManagerDelegate
扩展它们

如何从
AppDelegate
SceneDelegate
调用
GoogleMapsView
中的方法


在本例中,当位置更改事件通过
CLLocationManagerDelegate
发送到
AppDelegate
实例时,我想调用
.animate
方法,但问题实际上更一般。

而不是直接从外部调用
视图
方法,您应该稍微修改一下逻辑,只需在某个地方更改某种
状态
,然后让
视图
自行更新。看看这个算法:


经典(也是最糟糕的)方式:
  • 位置改变
  • 在应用程序委托中调用的委托方法(更好地重构到其他位置)
  • App delegate直接在
    视图
    上调用方法(您应该将对该
    视图的引用一直传递到App delegate)

  • 虽然上面的算法是您最初寻找的,但它不是最好的方法,我根本不推荐它!但是它将工作我制作并实现了CLLocationManager和MKMapView,它几乎与“地图”相同,希望它能帮助您:

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

    长答案

    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)
        }
    }
    
    struct ContentView: View {
    
        @ObservedObject var viewModel: ContentViewModel
    
        var body: some View {
            VStack {
                MapView(location: self.$viewModel.location)
            }
        }
    }
    
    首先,我创建了一个符合UIViewRepresentable协议的Mapview,正如您所做的,但添加了@Binding变量,这是我的“触发器”

    地图视图

    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)
        }
    }
    
    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{
    私有let locationManager=CLLocationManager()
    var代表:位置WorkerDelegate?
    let objectWillChange=PassthroughSubject()
    @发布的变量位置状态:CLAuthorizationStatus{
    意志{
    objectWillChange.send()
    }
    }
    @发布的var lastLocation:CLLocation{
    意志{
    objectWillChange.send()
    }
    }
    重写init(){
    super.init()
    self.locationManager.delegate=self
    //...
    }
    }
    协议位置WorkerDelegate{
    功能位置已更改(最后位置:CLLocation?)
    }
    扩展位置工作人员:CLLocationManagerDelegate{
    func locationManager(manager:CLLocationManager,didUpdateLocations位置:[CLLocation]){
    guard let location=locations.last else{return}
    self.lastLocation=位置
    //位置更改时:我使用我的代理->
    如果委托!=nil{
    委派!.locationChanged(lastLocation:lastLocation)
    }
    }
    }
    
    对于设置位置更改动画,位置数据是否处于视图中的
    @状态
    @ObservedObject
    变量中?这里的想法是,对此状态的更改将自动更新视图,因此视图的更改可以使用
    .animation()
    ViewModifier。将在GoogleMapsView对象(实际上是其内部地图视图)上调用.animate方法,以显示以不同位置为中心的地图。这并不是一个我可以控制的动画,因为它是由MapView本身提供的。更一般的问题是如何从AppDelegate或SceneDelegate调用视图方法。似乎我必须查看新SwiftUI范型的Combine元素。我没有机会查看GoogleMapsView,但是作为一个
    UIViewRepresentable
    我想知道它在这方面的可扩展性是什么。通过提供的链接不清楚您将调用什么、在哪里以及在什么条件下调用。在伪代码的注释中显示你想要实现什么。太棒了,谢谢你,这正是我想要的。我确实明白,在这个新的范例中,视图不应该通过获取引用的方式进行外部操纵,但在尝试使用Combine连接所有内容时迷失了方向。我知道这个线程已经存在几个月了,但我希望我可以问一个与从ViewModel访问视图相关的问题。使用UIViewRepresentable表示地图视图时,ViewModel访问所需地图视图对象以添加覆盖等的功能将消失。。。。我如何告诉uiview代表viewmodel添加覆盖或直接调用它来访问mapView对象?@Tim您可以传递另一个
    @Binding
    变量来控制覆盖可能
    @Binding var:showOverlay=false
    ,每次更新时,都会调用updateUIView,您决定是否显示覆盖。zgluis,感谢您的回复。我通过将我在viewmodel中创建的多段线设置为@Published,然后在updateUI中访问覆盖,在那里我可以访问mapview。谢谢我确实试着去“迅捷”,但在路上迷路了。@zgluis的例子说明了这一点。非常感谢。