UIViewRepresentable with Coordinator Example

Published by malhal on

There are not many examples of correctly using a Coordinator with UIViewRepresentable so thought I would share one here. You’ll see many examples including some from Apple that return Coordinator(self) which is completely wrong because the UIViewRepresentable is a struct value type which is recreated on every state change so the self will be an old version. The secret is to have the Coordinator hold 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