Swiftui UIViewRepresentable:“;在视图更新期间修改状态,这将导致未定义的行为;
我已经从MKMapView制作了一个简单的UIViewRepresentable。你可以滚动MavVIEW,屏幕将用中间的坐标更新。 以下是ContentView:Swiftui UIViewRepresentable:“;在视图更新期间修改状态,这将导致未定义的行为;,swiftui,Swiftui,我已经从MKMapView制作了一个简单的UIViewRepresentable。你可以滚动MavVIEW,屏幕将用中间的坐标更新。 以下是ContentView: import SwiftUI import CoreLocation let london = CLLocationCoordinate2D(latitude: 51.50722, longitude: -0.1275) struct ContentView: View { @State private var cent
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)
}
}
}
以下是地图视图:
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,以使地图滚动再次快速,并修复错误?如果将中心变量置于可观察对象中,速度问题似乎已得到解决。它消除了可能导致延迟的异步更新的需要
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
}
}
}
使用ObservedObject的上述解决方案将不起作用。虽然您不会再看到警告消息,但问题仍在发生。Xcode只是无法再提醒您它正在发生 ObservieObject中的已发布属性的行为与@State和@Binding几乎相同。也就是说,每当触发其
对象willupdate
发布服务器时,它们都会触发视图更新。更新@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
}
}
如果您确实不想使用ObserveObject,另一种方法是将shouldUpdateView
属性放入协调器中。尽管我仍然更喜欢使用viewModel,因为它使UIViewRepresentable不受多个@binding的影响。您还可以在外部使用ViewModel,并通过combine收听它
坦白地说,我很惊讶苹果公司在创建UIVIEW的时候没有考虑这个问题。
如果您需要使SwiftUI状态与视图更改保持同步,几乎所有UIKit视图都会遇到这个问题。。更新时,问题会更清楚地说明。这只是部分解决方案。它实际上已经被另一个投稿人发布,但被删除了。虽然这样可以防止出现错误消息,但MapView的滚动速度仍然很慢。我更新了代码以使用ObservieObject。它消除了异步更新的需要。该解决方案确实解决了所描述的问题。然而,我一直在问自己是否有更好的解决办法。从本质上讲,单身人士现在实际上是一个老式的代表,但却永远存在。@BaronSharktooth单身人士可以通过几行更改来删除。MapController在MVVM模式中作为一种ViewModel工作。此解决方案仅隐藏Xcode警告。它实际上并没有解决这个问题。实际上,每次移动贴图时都会导致循环。贴图将更改其可见区域,这将调用委托方法。然后,委托方法更新已发布的属性。更新发布的特性会导致重新加载视图,然后更新地图可见区域,从而使您重新启动整个循环。请参阅我在下面发布的解决方案。