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