Search code examples
iosswiftuimapboxmapbox-ios

Updating a SwiftUI Mapbox Map View to Show New Pin Annotations on a Button Press


I have a SwiftUI project in which I want to forward geocode an address to coordinates and update the Mapbox map view to display a pin annotation at that address's coordinates on a button press.

When pressing the button I created, the action seems to work but the map does not show a new pin anywhere even though my annotation array (which is used for pin placement) is updated with the information for the new address.

My content and map view are as follows:

MapView

import SwiftUI
import Mapbox



extension MGLPointAnnotation {
    convenience init(title: String, coordinate: CLLocationCoordinate2D) {
        self.init()
        self.title = title
        self.coordinate = coordinate
    }
}



    struct MapView: UIViewRepresentable {


    var mapView: MGLMapView = MGLMapView(frame: .zero, styleURL: MGLStyle.streetsStyleURL)



       @Binding var annotations: [MGLPointAnnotation]

    final class Coordinator: NSObject, MGLMapViewDelegate {
        var control: MapView

        init(_ control: MapView) {
            self.control = control
        }

        func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {

            return nil
        }


        func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
            return true
        }

    }

    func makeUIView(context: UIViewRepresentableContext<MapView>) -> MGLMapView {
        mapView.delegate = context.coordinator
        return mapView
    }


    func styleURL(_ styleURL: URL) -> MapView {
            mapView.styleURL = styleURL
            return self
        }

        func centerCoordinate(_ centerCoordinate: CLLocationCoordinate2D) -> MapView {
            mapView.centerCoordinate = centerCoordinate
            return self
        }

        func zoomLevel(_ zoomLevel: Double) -> MapView {
            mapView.zoomLevel = zoomLevel
            return self
        }

    func updateUIView(_ uiView: MGLMapView, context: UIViewRepresentableContext<MapView>) {
          updateAnnotations()
      }

      func makeCoordinator() -> MapView.Coordinator {
          Coordinator(self)
      }

      private func updateAnnotations() {
          if let currentAnnotations = mapView.annotations {
              mapView.removeAnnotations(currentAnnotations)
          }
          mapView.addAnnotations(annotations)
      }

}

ContentView

import SwiftUI
import Mapbox
import CoreLocation

struct ContentView: View {

    @ObservedObject var VModel = ViewModel()

    @ObservedObject var locationManager = LocationManager()

    var userLatitude: CLLocationDegrees {
        return (locationManager.lastLocation?.coordinate.latitude ?? 0)
     }

     var userLongitude: CLLocationDegrees {
        return (locationManager.lastLocation?.coordinate.longitude ?? 0)
     }

    var lat: Double {
        return (VModel.lat ?? 0)
    }
    var long: Double {
        return (VModel.lon ?? 0)
    }

    @State var searchedLocation: String = ""
    @State var annotations = [
        MGLPointAnnotation(title: "Mapbox", coordinate: .init(latitude: 37.791434, longitude: -122.396267))
    ]

    var body: some View {

        VStack{

            ZStack(alignment: Alignment(horizontal: .leading, vertical: .top)){

                MapView(annotations: $annotations).centerCoordinate(.init(latitude: self.lat, longitude: self.long)).zoomLevel(16).edgesIgnoringSafeArea(.all)
                VStack{
                    Button(action: {
                        self.VModel.fetchCoords(address: "address")


                        let delayInSeconds = 3.0
                        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayInSeconds) {
                            self.annotations.append(MGLPointAnnotation(title: "Home", coordinate: .init(latitude: self.lat, longitude: self.long)))

                        }

                        print("\(self.annotations)")

                    }, label: {Text("Press to update annotation")})
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Any information on updating the map to show new pin annotations on a button press is much appreciated. Please comment if more information is needed


Solution

  • Without knowledge of your VModel to LocationManager I stripped them out and just hard-coded some values for some annotations. So although the code below is a much simplified version of yours, you can see the addition of the third annotation when the button is pressed.

    It is a straightforward implementation where the view-model (AnnotationsVM) controlling the annotations is referenced as an @ObservedObject in the ContextView. It publishes the array of annotations. These are passed into the @Binding variable in the MapView and when that array changes (additions, deletions, etc) the updateUIView method updates the map.

    import SwiftUI
    import Mapbox
    
    class MapViewCoordinator: NSObject, MGLMapViewDelegate {
        func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
            return true
        }
    }
    
    struct MapView: UIViewRepresentable {
        @Binding var annos: [MGLPointAnnotation]
    
        func makeUIView(context: Context) -> MGLMapView {
            let map = MGLMapView()
            map.styleURL = MGLStyle.outdoorsStyleURL
            map.delegate = context.coordinator
    
            let location = CLLocation(latitude: 40.763783, longitude: -73.973133)
            map.setCenter(location.coordinate, zoomLevel: 13, animated: true)
    
            return map
        }
    
        func updateUIView(_ uiView: MGLMapView, context: Context) {
            if let currentAnnotations = uiView.annotations {
                uiView.removeAnnotations(currentAnnotations)
            }
            uiView.addAnnotations(annos)
        }
    
        func makeCoordinator() -> MapViewCoordinator {
            return MapViewCoordinator()
        }
    }
    
    struct ContentView: View {
        @ObservedObject var annotationsVM = AnnotationsVM()
    
        var body: some View {
            NavigationView {
                MapView(annos: $annotationsVM.annos)
                    .navigationBarTitle(Text("Hello"))
                    .navigationBarItems(trailing: Button(action: {
                        self.annotationsVM.addNextAnnotation()
                    }, label: {
                        Image(systemName: "plus.circle")
                    }))
            }
    
        }
    }
    
    class AnnotationsVM: ObservableObject {
        @Published var annos = [MGLPointAnnotation]()
    
        init() {
            var annotation = MGLPointAnnotation()
            annotation.title = "Apple Store"
            annotation.coordinate = CLLocationCoordinate2D(latitude: 40.77, longitude: -73.98)
            annotation.subtitle = "Think Different"
            annos.append(annotation)
    
            annotation = MGLPointAnnotation()
            annotation.title = "Shoe Store"
            annotation.coordinate = CLLocationCoordinate2D(latitude: 40.78, longitude: -73.98)
            annotation.subtitle = "Shoe Different"
            annos.append(annotation)
        }
    
        func addNextAnnotation() {
            let newAnnotation = MGLPointAnnotation()
            newAnnotation.title = "New Annotation"
            newAnnotation.coordinate = CLLocationCoordinate2D(latitude: 40.763783, longitude: -73.973133)
            newAnnotation.subtitle = "Ben Button"
            annos.append(newAnnotation)
        }
    }