UIViewRepresentable with Coordinator Example
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)
}
}
}