UIViewRepresentable with Coordinator Example

Published by malhal on

There are not many examples of correctly using a Coordinator with UIViewRepresentable so thought I’d share one on mine. You’ll see many examples including some from Apple that return Coordinator(self) which I believe is a mistake because the UIViewRepresentable is a struct value type which is recreated on every state change so the self will be an old version. I believe the trick is to have the Coordinator own the UIView and in updateUIView you should update both the Coordinator and the UIView with the latest lets or @Binding vars in the new version of the UIViewRepresentable.

struct MyMap: UIViewRepresentable {
    @Binding var userTrackingMode: MapUserTrackingMode
    
    func makeCoordinator() -> Coordinator {
        Coordinator()
    }
    
    func makeUIView(context: Context) -> MKMapView {
        context.coordinator.mapView
    }
    
    func updateUIView(_ uiView: MKMapView, context: Context) {
        // MKMapView has a strange design that the delegate is called when setting manually so we need to prevent an infinite loop
        context.coordinator.userTrackingModeChanged = nil
        uiView.userTrackingMode = userTrackingMode == .follow ? MKUserTrackingMode.follow : MKUserTrackingMode.none
        context.coordinator.userTrackingModeChanged = { mode in
            userTrackingMode = mode == .follow ? MapUserTrackingMode.follow : MapUserTrackingMode.none
        }
    }
    
    class Coordinator: NSObject, MKMapViewDelegate {
        
        lazy var mapView: MKMapView = {
            let mv = MKMapView()
            mv.delegate = self
            return mv
        }()
        
        var userTrackingModeChanged: ((MKUserTrackingMode) -> Void)?
        
        func mapView(_ mapView: MKMapView, didChange mode: MKUserTrackingMode, animated: Bool) {
            userTrackingModeChanged?(mode)
        }
    }
}
Categories: MapKitSwiftUI