0

I'm stuck with the really strange problem. I'm implementing map into my SwiftUI app. It should act like a normal map (drag, scroll and so on). When changing position (that is binding point) the app gets an address via geocoder.

Also user can click "Change" button and enter address manually (with autocompletion). After selecting the address, the map should move to the reverse geocoded point.

Built-in SwiftUI Map() is a good thing, but... it's unreal to make it show buildings. And in the app it's something that matters. So, going with UIViewRepresentable gives me another strange problem.

If I set the center coordinate in UpdateUIView, the map stops any interactivity. Otherwise changing the address manually doesn't work.

What could be wrong with this?

struct MapView: UIViewRepresentable {
    @Binding var point: CLLocationCoordinate2D
    
    func makeUIView(context: Context) -> MKMapView {
        let mapView = MKMapView()
        mapView.showsBuildings = true
        mapView.delegate = context.coordinator
        
        return mapView
    }
    
    func updateUIView(_ uiView: MKMapView, context: Context) {
        // uiView.setCenter(point, animated: true)
    }
    
    func makeCoordinator() -> MapView.Coordinator {
        Coordinator(self)
    }
    
    class Coordinator: NSObject, MKMapViewDelegate {
        var parent: MapView
        
        init(_ parent: MapView) {
            self.parent = parent
        }
        
        func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
            self.parent.point = mapView.centerCoordinate
        }
    }
}

I tried wrapping everything into DispatchQueue.main.async {} - not working (and honestly I don't think it could)

I also tried this solution, but it worked neither: https://www.reddit.com/r/SwiftUI/comments/kti9r9/uiviewrepresentable_how_to_update_bindings/

Alex Holxey
  • 73
  • 10
  • 2
    Your problem is you have a feedback loop that is grinding your UI to a halt. If you put `print("updateUIView called") into `func UpdateUIView()` and `print("mapViewDidChangeVisibleRegion called") into `func mapViewDidChangeVisibleRegion()` you will see the problem. Essentially, you have a binding, `point`, that sets your center coordinate. You also update that center coordinate, `point`, when you move the map. Then you update `point` again in `func mapViewDidChangeVisibleRegion`, which then causes a call to `func updateUIView()` And this just keeps repeating. What do you want to accomplish? – Yrb Sep 26 '21 at 17:29
  • Thank you for this hint! I want to change the point in two ways: moving the map and providing it an external value from geocoder. And I don't get, how else I can do that – Alex Holxey Sep 26 '21 at 21:59
  • Then why do you need the Binding? – Yrb Sep 26 '21 at 22:00
  • Well, the structure is kind of that: I have a View with a ViewModel where all the stuff is happening. Inside this View I have a Map and additional controls (address + edit button). And Map is our UIViewRepresentable. So, I bind a map to `viewModel.point` to change it. – Alex Holxey Sep 26 '21 at 22:09

1 Answers1

0

I also had the same problem. I solved this using @state. So every time the mapView changes, the corresponding function of the coordinator is definitely called. Hope it helps.

struct YourView: View {
    @State mapView: MKMapView = .init()
    @State var point: CLLocationCoordinate2D = [...]

    var body: some View {
        MapView(mapView: $mapView, point: $point)
        ...
}

struct MapView: UIViewRepresentable {

    @Binding var mapView: MKMapView
    @Binding var point: CLLocationCoordinate2D

    func makeUIView(context: Context) -> MKMapView {
        self.mapView = MKMapView()
        self.mapView.showsBuildings = true
        self.mapView.delegate = context.coordinator
    
        return self.mapView
    }
    ...
    func updateUIView(_ uiView: MKMapView, context: Context) {
        // uiView.setCenter(point, animated: true)
    }

    func makeCoordinator() -> MapView.Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, MKMapViewDelegate {
        var parent: MapView
    
        init(_ parent: MapView) {
            self.parent = parent
        }
    
        func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
            self.parent.point = self.mapView.centerCoordinate
        }
    }
}