I have a map which loads annotations from the Google API, when the map initially loads all the annotations they are 'placed' as seen through the print in the console, however they won't show up on the map until I move the map once. Does anyone know if I need to call a method to update the map after placing the annotations?
struct ContentView: View {
var locationSearch = LocationSearch()
@State private var mapView = MapView()
@State var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: -33.7944, longitude: 151.2649), span: MKCoordinateSpan(latitudeDelta: 0.015, longitudeDelta: 0.015))
@EnvironmentObject var sheetManager: SheetManager
var body: some View {
mapView
.popup(with: SheetManager())
.frame(width: UIScreen.screenWidth, height: UIScreen.screenHeight)
}
}
struct MapView: UIViewRepresentable {
@State var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: -33.7944, longitude: 151.2649), span: MKCoordinateSpan(latitudeDelta: 0.015, longitudeDelta: 0.015))
func updateUIView(_ uiView: MKMapView, context: Context) {
print("FLF: MapView updated")
uiView.setNeedsDisplay()
}
var locationManager = CLLocationManager()
let mapView = MKMapView(frame: CGRect(x: 0, y: 0, width: UIScreen.screenWidth, height: UIScreen.screenHeight))
func setupManager() {
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.requestAlwaysAuthorization()
}
func makeUIView(context: Context) -> MKMapView {
setupManager()
mapView.region = ContentView().region
mapView.showsUserLocation = true
mapView.userTrackingMode = .follow
mapView.delegate = context.coordinator // set the delegate to the coordinator
placeMarkersForRegion(region: region)
return mapView
}
func placeMarkersForRegion(region: MKCoordinateRegion) {
var locationSearch = LocationSearch()
locationSearch.performSearch(region: region) { venues in
print("FLF: Placing \(venues.count) marker(s)")
for marker in venues {
let annotation = MKPointAnnotation()
annotation.coordinate = marker.location
annotation.title = marker.name
mapView.addAnnotation(annotation)
}
}
}
func makeCoordinator() -> MapViewCoordinator {
MapViewCoordinator(self) // pass self to the coordinator so it can call `regionDidChangeAnimated`
}
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
// Use the 'coordinate' property to get the current location of the map view
let currentRegion = mapView.region
print("FLF: Map has moved")
self.placeMarkersForRegion(region: currentRegion)
// Do something with the current region (e.g. update a state variable or perform a search)
}
}
class MapViewCoordinator: NSObject, MKMapViewDelegate {
var parent: MapView // add a property to hold a reference to the parent view
init(_ parent: MapView) {
self.parent = parent
}
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
// Call the parent's implementation of this method
parent.mapView(mapView, regionDidChangeAnimated: animated)
}
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
view.canShowCallout = true
view.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
// Get the tapped annotation
guard let annotation = view.annotation else { return }
// Print the title of the annotation
print(annotation.title ?? "Unknown")
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
print("FLF: Marker tapped")
}
}
The UIViewRepresentable
and Coordinator
aren't implemented correctly. E.g. makeUIView
has to init it, but you are initing it as a property on the struct which is immediately lost. Also MapViewCoordinator(self)
is a mistake because self, i.e. the struct, is immediately disgarded after SwiftUI has updated.
Another issue is the @State
shouldn't hold a View like how your ContentView
has a @State
for the MapView
.
Here is an example of how to use MKMapView
with UIViewRepresentable
:
struct MKMapViewRepresentable: UIViewRepresentable {
@Binding var userTrackingMode: MapUserTrackingMode
func makeCoordinator() -> Coordinator {
Coordinator()
}
func makeUIView(context: Context) -> MKMapView {
context.coordinator.mapView
}
func updateUIView(_ uiView: MKMapView, context: Context) {
// MKMapView has a strange design that the delegate is called when setting manually so we need to prevent an infinite loop
context.coordinator.userTrackingModeChanged = nil
uiView.userTrackingMode = userTrackingMode == .follow ? MKUserTrackingMode.follow : MKUserTrackingMode.none
context.coordinator.userTrackingModeChanged = { mode in
userTrackingMode = mode == .follow ? MapUserTrackingMode.follow : MapUserTrackingMode.none
}
}
class Coordinator: NSObject, MKMapViewDelegate {
lazy var mapView: MKMapView = {
let mv = MKMapView()
mv.delegate = self
return mv
}()
var userTrackingModeChanged: ((MKUserTrackingMode) -> Void)?
func mapView(_ mapView: MKMapView, didChange mode: MKUserTrackingMode, animated: Bool) {
userTrackingModeChanged?(mode)
}
}
}