【问题标题】:How to invoke a method in a view in SwiftUI如何在 SwiftUI 的视图中调用方法
【发布时间】:2020-02-22 19:54:16
【问题描述】:

刚刚开始使用 SwiftUI。

我在ContentView 中有一个GoogleMapsView 使用CLLocationManager,我通过使用CLLocationManagerDelegate 扩展它们来捕获AppDelegateSceneDelegate 类中的事件。

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

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

【问题讨论】:

  • 对于位置变化的动画,位置数据是否可以在视图中的@State@ObservedObject 变量中?这里的想法是更改此状态会自动更新视图,因此视图的更改可以使用.animation() ViewModifier。
  • 将在 GoogleMapsView 对象(实际上是它的内部 MapView)上调用 .animate 方法,以显示以不同位置为中心的地图。这不是我可以控制的动画,因为它是由 MapView 本身提供的。更通用的问题是如何从 AppDelegate 或 SceneDelegate 调用 View 方法。
  • 看来我得看看新的 SwiftUI 范式的 Combine 元素了。
  • 我还没有机会研究 GoogleMapsView,但作为 UIViewRepresentable 我想知道它在这方面的可扩展性是什么。
  • 通过提供的链接不清楚您要打电话的内容、地点和条件。用伪代码在 cmets 中显示您想要实现的目标。

标签: swiftui google-maps-sdk-ios


【解决方案1】:

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

简答:声明@Binding var foo: Any,您将能够在每次 foo 更改时在 GoogleMapView 中进行更改,在这种情况下 foo 是您的位置,因此您可以在每次 foo 时调用 animate更新了。

长答案

首先我创建了一个符合 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)
    }
}

然后我将我的 MapView 放在我的 ContentView 中,传递一个 location 参数,我将在下面解释:

内容视图

struct ContentView: View {

    @ObservedObject var viewModel: ContentViewModel

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

在我的 ViewModel 中,我使用委托处理位置更改,下面是 cmets 中包含更多详细信息的代码:

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

【讨论】:

  • 太棒了,谢谢,这正是我所追求的。我确实明白,在这个新的范例中,视图不应该通过获取引用在外部进行操作,而是在尝试使用 Combine 连接所有内容时迷失了。
  • 我知道这个帖子有几个月的历史了,但希望我能问一个与从 ViewModel 访问视图相关的问题。当使用 UIViewRepresentable 表示 MapView 时,ViewModel 访问所需的 mapView 对象以添加叠加层等的能力消失了....如何告诉 uiview 代表视图模型添加叠加层或直接调用它访问 mapView 对象?
  • @Tim 你可以传递另一个@Binding 变量来控制你的覆盖可能@Binding var :showOverlay = false,每次你更新它时都会调用updateUIView,你决定是否显示你的覆盖。
  • zgluis,感谢您的回复。我通过将我在 viewmodel 中创建的折线设置为 @Published 然后在我可以访问 mapview 的 updateUI 中访问覆盖层来让它工作。谢谢!
【解决方案2】:

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


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

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

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


SwiftUI 方式:

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

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

【讨论】:

  • 我确实尝试过使用“SwiftUI”,但在途中迷路了。 @zgluis 的例子说明了一切。谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多