如何在 SwiftUI 的视图中调用方法
How to invoke a method in a view in SwiftUI
刚刚开始使用 SwiftUI。
我在 ContentView
中有一个 GoogleMapsView
使用 CLLocationManager
我通过使用 CLLocationManagerDelegate
.
扩展它们来捕获 AppDelegate
或 SceneDelegate
class 中的事件
如何从 AppDelegate
或 SceneDelegate
调用 GoogleMapsView
中的方法?
在这个例子中,当位置更改事件通过 CLLocationManagerDelegate
发送到 AppDelegate
实例时,我想调用 .animate
方法,但问题确实更笼统。
与其直接从外部调用 View
方法,不如稍微修改一下逻辑,只在某处更改某种 state
并让 View
自行更新。看看这个算法:
经典(也是最糟糕)的方式:
- 位置已更改
- 在应用委托中调用委托方法(最好重构到其他地方)
- 应用程序委托直接在
view
上调用方法(您应该将对该 view
的引用一直传递给应用程序委托)
虽然上面的算法是你原本要找的,但不是最好的方法,我完全不推荐!但它会起作用 ♂️
SwiftUI 方式:
- 位置已更改
- 负责对象中调用的委托方法(可能是单例位置位置管理器实例♂️)
- 位置管理器在某处更新
State
。 (可能是自身内部的 ObservedObject
变量或 EnvironmentObject
等)
- 订阅更改的所有视图属性 将通知更改
- 所有通知的视图都会自行更新。
这是应该做的。但是实现这一点的方法不止一种,您应该考虑自己的喜好来选择最适合您的方法。
我做了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)
}
}
}
刚刚开始使用 SwiftUI。
我在 ContentView
中有一个 GoogleMapsView
使用 CLLocationManager
我通过使用 CLLocationManagerDelegate
.
AppDelegate
或 SceneDelegate
class 中的事件
如何从 AppDelegate
或 SceneDelegate
调用 GoogleMapsView
中的方法?
在这个例子中,当位置更改事件通过 CLLocationManagerDelegate
发送到 AppDelegate
实例时,我想调用 .animate
方法,但问题确实更笼统。
与其直接从外部调用 View
方法,不如稍微修改一下逻辑,只在某处更改某种 state
并让 View
自行更新。看看这个算法:
经典(也是最糟糕)的方式:
- 位置已更改
- 在应用委托中调用委托方法(最好重构到其他地方)
- 应用程序委托直接在
view
上调用方法(您应该将对该view
的引用一直传递给应用程序委托)
虽然上面的算法是你原本要找的,但不是最好的方法,我完全不推荐!但它会起作用 ♂️
SwiftUI 方式:
- 位置已更改
- 负责对象中调用的委托方法(可能是单例位置位置管理器实例♂️)
- 位置管理器在某处更新
State
。 (可能是自身内部的ObservedObject
变量或EnvironmentObject
等) - 订阅更改的所有视图属性 将通知更改
- 所有通知的视图都会自行更新。
这是应该做的。但是实现这一点的方法不止一种,您应该考虑自己的喜好来选择最适合您的方法。
我做了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)
}
}
}