How do I declare a view so it's initialization and itself are NOT in the representable view?

Viewed 129

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()
        
    }
}
1 Answers

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()
        }
    }
}
Related