3

I'm trying to create a SwiftUI view with an MKMapView and a button to navigate to the user location. Below is the entirety of my code. I created a view called MapView which is conforms to UIViewRepresentable to hold my MKMapView. I have a location button that, on tapping, sets shouldNavigateToUserLocation to true. This causes the UI to reload, and my MapView navigates to the user location if shouldNavigateToUserLocation is true. Then, it sets shouldNavigateToUserLocation to false, so that the MapView is not constantly moving to the user location on ever state change.

This approach seems to work when running on real devices, but I get the warning "Modifying state during view update, this will cause undefined behavior." on line 87, which is shouldNavigateToUserLocation = false. This is understandable, but my question is, how can I avoid this? I can't seem to find a way to restructure my code so I'm not violating the rule of not modifying state during a view update, while still having the map navigate to the user location when and only when the user presses the button.

I have tried a couple of different approaches, but I mainly get stuck with the problem of neither my MapView nor my Controller class actually having direct access to the MKMapView. I understand why that is in SwiftUI, but it really limits what I can do.

Here is the entirety of my code:

import SwiftUI
import MapKit

struct ContentView: View {

  @State var currentlyDisplayingLocationAuthorizationRequest = false
  @State var shouldNavigateToUserLocation = false

  let locationManager = CLLocationManager()

  var body: some View {
    ZStack {
      MapView(currentlyDisplayingLocationAuthorizationRequest: $currentlyDisplayingLocationAuthorizationRequest,
              shouldNavigateToUserLocation: $shouldNavigateToUserLocation)

      HStack {
        Spacer()
        VStack {
          Spacer()
          Button(action: {
            self.checkForLocationAuthorizationAndNavigateToUserLocation()
          }) {
            Image(systemName: "location")
              .imageScale(.large)
              .accessibility(label: Text("Locate Me"))
              .padding()
          }
          .background(Color.gray)
          .cornerRadius(10)
          .padding()
        }
      }
    }
    .onAppear {
      self.shouldNavigateToUserLocation = true
    }
  }

  func checkForLocationAuthorizationAndNavigateToUserLocation() {
    currentlyDisplayingLocationAuthorizationRequest = false

    if CLLocationManager.authorizationStatus() == .notDetermined {
      print("location authorization not determined")
      currentlyDisplayingLocationAuthorizationRequest = true
      locationManager.requestWhenInUseAuthorization()
      return
    }

    shouldNavigateToUserLocation = true
  }

}

struct MapView: UIViewRepresentable {

  @Binding var currentlyDisplayingLocationAuthorizationRequest: Bool
  @Binding var shouldNavigateToUserLocation: Bool

  let locationManager = CLLocationManager()

  func makeUIView(context: Context) -> MKMapView {
    let map = MKMapView()
    map.delegate = context.coordinator
    map.showsUserLocation = true
    return map
  }

  func updateUIView(_ uiView: MKMapView, context: Context) {
    if !currentlyDisplayingLocationAuthorizationRequest && shouldNavigateToUserLocation {
      moveToUserLocation(map: uiView)
    }
  }

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

  // MARK: Location -

  private func moveToUserLocation(map: MKMapView) {
    guard let location = locationManager.location else { return }

    let region = MKCoordinateRegion(center: location.coordinate,
                                    span: MKCoordinateSpan(latitudeDelta: 0.02,
                                                           longitudeDelta: 0.02))
    map.setRegion(region, animated: true)
    shouldNavigateToUserLocation = false
  }

  // MARK: Coordinator -

  final class Coordinator: NSObject, MKMapViewDelegate {

    var control: MapView

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

  }

}
Eugene
  • 3,417
  • 5
  • 25
  • 49
  • I answered the question in another post: https://stackoverflow.com/questions/58398437/how-to-add-a-move-back-to-user-location-button-in-swiftui/67956924#67956924, or you can refer to my demo project in my github: https://github.com/zhihuitang/UserLocationDemo – DàChún Jun 13 '21 at 09:55

1 Answers1

0

It's not exactly what you are searching for, but you should take a look at my recent answer to another related question (How to add a move back to user location button in swiftUI?). I think it can help you, or others if they are facing the same problem.

Rémi B.
  • 2,410
  • 11
  • 17