I have a UIKit representable view for using Mapbox in my SwiftUI app and control the mapView from inside the Coordinator by passing the MapboxViewRepresentable View itself when making the coordinator.
The problem I am having is that the mapView, its coordinator and subscriptions are all being created TWICE! While trying to figure out what the problem was someone commented that my problem was "holding on to the view in a variable in the representable. Then the initialization and the view are in the make. Don’t put it on a variable at struct level"
So I adjusted my code and passed the mapView inside makeUIView instead of declaring it in MapboxMapViewRepresentable and used didSet inside of the Coordinator to create/save the subscriptions once the view was passed.
Since I need to manipulate the environmentObjects via MapboxMapViewRepresentable.Coordinator methods, I have a control property (also REQUIRED by super.init() ) declared in the Coordinator.
I have no idea why everything is still being created twice, I don't think the fact I used extensions is doing it??
Anything else I could think of to check???
Thank you
PARENT STRUCT & REPRESENTABLE
struct MapboxMapView: View {
@ObservedObject var mapVM : MapViewModel
@ObservedObject var popupVM : PopupProfileViewModel
var body: some View {
ZStack{
MapboxMapViewRepresentable()
.environmentObject(self.mapVM)
.environmentObject(self.popupVM)
}
}
}
struct MapboxMapViewRepresentable: UIViewRepresentable {
@EnvironmentObject var session: SessionStore
@EnvironmentObject var mapVM : MapViewModel
@EnvironmentObject var popupVM: PopupProfileViewModel
func makeCoordinator() -> Coordinator {
let coordinator = Coordinator(control: self)
return coordinator
}
func makeUIView(context: Context) -> MGLMapView {
let view = MGLMapView(frame: .zero, styleURL: URL(string: MAPBOX_STYLE_URL))
context.coordinator.mapView = view
view.showsUserLocation = true
view.setCenter(CLLocationCoordinate2D(latitude: mapVM.usersCurrentLat, longitude: mapVM.usersCurrentLong), zoomLevel: MapViewModel.startAtZoomLevel, animated: false)
view.delegate = context.coordinator
view.maximumZoomLevel = MapViewModel.maxZoomLevel
view.allowsRotating = false
view.direction = 0.0
view.allowsTilting = false
mapVM.zoomLevel = view.zoomLevel
return view
}
func updateUIView(_ mapView: MGLMapView, context: Context) { }
}
Coordinator (extension of MapboxMapViewRepresentable)
extension MapboxMapViewRepresentable {
class Coordinator: NSObject {
var control: MapboxMapViewRepresentable
let locationManager = CLLocationManager()
var subscriptions = Set<AnyCancellable>()
// MARK: NEW
var mapView : MGLMapView? {
didSet {
guard let mapView = mapView else { return }
// all of these run twice since map and coordinator are created twice
control.mapVM.placePublisher.sink { bool in
guard let self = self else { return }
// control.mapVM.doSomething
print(bool)
}.store(in: &subscriptions)
Self.showRandomPlace.sink { location in
// mapView.setCenter....
// control.popupVM.doSomething()
print(location)
}.store(in: &subscriptions)
control.mapVM.newUserSelectedPublisher.sink { nearestPlaceCoords in
DispatchQueue.main.async {
mapView.zoomLevel = 15.9
mapView.setCenter(CLLocationCoordinate2D(latitude: nearestPlaceCoords.lat, longitude: nearestPlaceCoords.long), animated: false)
}
}.store(in: &subscriptions)
}
}
// publishes zoomLevel with coordiantor method regionIsChanging to update viewModel with current zoomLevel
let zoomLevelPublisher = PassthroughSubject<Double, Never>()
var previosZoomLevel = MapViewModel.maxZoomLevel
init(control: MapboxMapViewRepresentable) {
self.control = control
super.init()
setupLocationManager()
// set inital map view's center to user's/device's current location
if let coordinate = locationManager.location?.coordinate {
guard let mapView = mapView else { return }
self.setCenterAt(coordinate: coordinate)
control.mapVM.mapCenterCoordinate = mapView.centerCoordinate
}
} // END INIT
deinit {
subscriptions.forEach{$0.cancel()}
subscriptions.removeAll()
}
}
}
How delegate methods are declared
extension MapboxMapViewRepresentable.Coordinator {
func mapView(_: MGLMapView, didFinishLoading style: MGLStyle) {
print("DID FINISH LOADING STYLE")
self.control.mapVM.didFinishLoadingUrlStyle = true
self.control.mapVM.loadCurrentUsersPlaces()
self.control.mapVM.loadNearestPlaces()
}
}