Swiftui UIViewRepresentable:“;在视图更新期间修改状态,这将导致未定义的行为;

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

我已经从MKMapView制作了一个简单的UIViewRepresentable。你可以滚动MavVIEW,屏幕将用中间的坐标更新。 以下是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)
        }
    }
}
以下是地图视图:

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警告。它实际上并没有解决这个问题。实际上,每次移动贴图时都会导致循环。贴图将更改其可见区域,这将调用委托方法。然后,委托方法更新已发布的属性。更新发布的特性会导致重新加载视图,然后更新地图可见区域,从而使您重新启动整个循环。请参阅我在下面发布的解决方案。