我从 MKMapView 制作了一个简单的 UIViewRepresentable。您可以滚动地图视图,屏幕将更新为中间的坐标。
这是内容视图:
import SwiftUI
import CoreLocation
let london = CLLocationCoordinate2D(latitude: 51.50722, longitude: -0.1275)
struct ContentView: View {
@State private var center = london
var body: some View {
VStack {
MapView(center: self.$center)
HStack {
VStack {
Text(String(format: "Lat: %.4f", self.center.latitude))
Text(String(format: "Long: %.4f", self.center.longitude))
}
Spacer()
Button("Reset") {
self.center = london
}
}.padding(.horizontal)
}
}
}
Run Code Online (Sandbox Code Playgroud)
这是地图视图:
struct MapView: UIViewRepresentable {
@Binding var center: CLLocationCoordinate2D
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ uiView: MKMapView, context: Context) {
uiView.centerCoordinate = self.center
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, MKMapViewDelegate {
var parent: MapView
func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
parent.center = mapView.centerCoordinate
}
init(_ parent: MapView) {
self.parent = parent
}
}
}
Run Code Online (Sandbox Code Playgroud)
点击重置按钮只需将 mapView.center 设置为 london 即可。当前方法将使地图滚动速度超慢,并且当点击按钮时,会导致错误“在视图更新期间修改状态,这将导致未定义的行为”。
重置坐标应如何传达给 MKMapView,以便地图滚动再次快速,并修复错误?
上述使用 ObservedObject 的解决方案将不起作用。虽然您不会再看到警告消息,但问题仍然存在。Xcode 无法再警告您它的发生。
ObservableObjects 中已发布属性的行为几乎与 @State 和 @Binding 相同。也就是说,只要objectWillUpdate
触发发布者,它们就会触发视图更新。当 @Published 属性更新时,这种情况会自动发生。您也可以自己手动触发它objectWillChange.send()
因此,可以使属性不会自动导致视图状态更新。我们可以利用它来防止 UIViewRepresentable 和 UIViewControllerRepresentable 结构的不必要的状态更新。
这是一个当您从 MKMapViewDelegate 方法更新其视图模型时不会循环的实现:
struct MapView: UIViewRepresentable {
@ObservedObject var viewModel: Self.ViewModel
func makeUIView(context: Context) -> MKMapView{
let mapview = MKMapView()
mapview.delegate = context.coordinator
return mapview
}
func updateUIView(_ uiView: MKMapView, context: Context) {
// Stop update loop when delegate methods update state.
guard viewModel.shouldUpdateView else {
viewModel.shouldUpdateView = true
return
}
uiView.centerCoordinate = viewModel.centralCoordinate
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, MKMapViewDelegate {
private var parent: MapView
init(_ parent: MapView) {
self.parent = parent
}
func mapViewDidChangeVisibleRegion(_ mapView: MKMapView){
// Prevent the below viewModel update from calling itself endlessly.
parent.viewModel.shouldUpdateView = false
parent.viewModel.centralCoordinate = mapView.centerCoordinate
}
}
class ViewModel: ObservableObject {
@Published var centerCoordinate: CLLocationCoordinate2D = .init(latitude: 0, longitude: 0)
var shouldUpdateView: Bool = true
}
}
Run Code Online (Sandbox Code Playgroud)
如果您确实不想使用 ObservableObject,另一种方法是将属性shouldUpdateView
放入协调器中。尽管我仍然更喜欢使用 viewModel,因为它使您的 UIViewRepresentable 免受多个 @Bindings 的影响。您还可以在外部使用 ViewModel 并通过组合来收听它。
老实说,我很惊讶苹果在创建 UIViewRepresentable 时没有考虑到这个确切的问题。
如果您需要使 SwiftUI 状态与视图更改保持同步,几乎所有 UIKit 视图都会遇到这个问题。
归档时间: |
|
查看次数: |
4183 次 |
最近记录: |