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"
That makes sense but how would I restructure my code?? Would the mapView become a global variable that can be manipulated everywhere??
I am not sure how to properly restructure this so that the mapView is NOT stored in the representable view AND how I should re-configure how I control the map; For example adding annotations, animating its position, etc...
Right now I control the map from the variable declared in the representable: control.mapView.doSomething AND access its view model the same way: control.mapVM.property, control.mapVM.func
Thank you
Container View
struct MapboxMapView: View {
@ObservedObject var mapVM : MapViewModel
@ObservedObject var popupVM : PopupProfileViewModel
var body: some View {
ZStack(alignment: .bottom) {
MapboxMapViewRepresentable()
.environmentObject(self.mapVM)
.environmentObject(self.popupVM)
}
}
}
REPRESENTABLE VIEW AND COORDINATOR
struct MapboxMapViewRepresentable: UIViewRepresentable {
@EnvironmentObject var session: SessionStore
@EnvironmentObject var mapVM : MapViewModel
@EnvironmentObject var popupVM: PopupProfileViewModel
let mapView: MGLMapView = MGLMapView(frame: .zero, styleURL: URL(string: MAPBOX_STYLE_URL))
// Where should I decleare this view??? Can it be a global???
func makeCoordinator() -> Coordinator {
let coordinator = Coordinator(control: self)
return coordinator
}
func makeUIView(context: Context) -> MGLMapView {
return mapView
}
func updateUIView(_ mapView: MGLMapView, context: Context) {/code/}
class Coordinator: NSObject {
let locationManager = CLLocationManager()
var control: MapboxMapViewRepresentable
var subscriptions = Set<AnyCancellable>()
init(control: MapboxMapViewRepresentable) {
self.control = control
super.init()
setupLocationManager()
// subscriber receives data via publisher to manipulate map
control.mapVM.addAnnotations.sink { annotations in
control.mapView.addAnnotations(annotations)
// ^ aways runs twice because current structure
// make two views and coordinators
}.store(in: &subscriptions)
}
deinit {
subscriptions.forEach{$0.cancel()}
subscriptions.removeAll()
}
}
}
CodePudding user response:
You can pass a reference to the UIView
to your Coordinator
. That might look like this:
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
return view
}
func updateUIView(_ mapView: MGLMapView, context: Context) {
//code
}
class Coordinator: NSObject {
let locationManager = CLLocationManager()
var control: MapboxMapViewRepresentable
var subscriptions = Set<AnyCancellable>()
var mapView : MGLMapView? = nil {
didSet {
// subscriber receives data via publisher to manipulate map
mapView?.addAnnotations.sink { annotations in
//annotation sink
}.store(in: &subscriptions)
}
}
init(control: MapboxMapViewRepresentable) {
self.control = control
super.init()
setupLocationManager()
}
}
}