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