我从MKMapView中创建了一个简单的UIViewRepresentable。您可以滚动地图视图,屏幕将更新为中间的坐标。
这是ContentView:
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)
}
}
}
这是MapView:
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
}
}
}
点击重置按钮,只需将mapView.center设置为london。当前方法将使地图滚动变得非常缓慢,并且在轻按按钮时会导致错误“在视图更新期间修改状态,这将导致未定义的行为。”
应如何将坐标重置传递给MKMapView,以使地图再次快速滚动且错误得到修复?
答案 0 :(得分:1)
上述带有 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
}
}
如果您真的不想使用 ObservableObject,另一种方法是将 shouldUpdateView
属性放入您的协调器中。尽管我仍然更喜欢使用 viewModel,因为它可以让您的 UIViewRepresentable 不受多个 @Bindings 的影响。您也可以在外部使用 ViewModel 并通过组合收听它。
老实说,我很惊讶苹果在创建 UIViewRepresentable 时没有考虑这个确切的问题。
如果您需要保持 SwiftUI 状态与视图更改同步,几乎所有 UIKit 视图都会遇到这个问题。
答案 1 :(得分:0)
如果将中心变量放在可观察对象中,则速度问题似乎已解决。它消除了可能导致延迟的异步更新需求。
import SwiftUI
import CoreLocation
import MapKit
//let london = CLLocationCoordinate2D(latitude: 51.50722, longitude: -0.1275)
class MapController: ObservableObject{
@Published var center: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: 51.50722, longitude: -0.1275)
func resetCenter(){
center = CLLocationCoordinate2D(latitude: 51.50722, longitude: -0.1275)
}
}
struct MapViewCenter: View {
//@State private var center = london
@ObservedObject var mapCont: MapController = MapController()
@State var refresh: Bool = false
var body: some View {
VStack {
MapView(refresh: $refresh, mapCont: mapCont)
HStack {
VStack {
Text(String(format: "Lat: %.4f", self.mapCont.center.latitude))
Text(String(format: "Long: %.4f", self.mapCont.center.longitude))
}
Spacer()
Button("Reset") {
print("resetButton")
self.mapCont.resetCenter()
self.refresh.toggle()
print("resetButton :: center = \(self.mapCont.center)")
}
}.padding(.horizontal)
}
}
}
struct MapView: UIViewRepresentable {
@Binding var refresh: Bool
var mapCont: MapController
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ uiView: MKMapView, context: Context) {
uiView.centerCoordinate = self.mapCont.center
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, MKMapViewDelegate {
var parent: MapView
func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
parent.mapCont.center = mapView.centerCoordinate
}
init(_ parent: MapView) {
self.parent = parent
}
}
}