0

I'm trying to mimic some functionality of Google Maps, where upon launching the app, the camera is moved to the current location of the user, provided the required perms have been granted.

I have implemented this using a custom current location button, which works fine taking the camera to the users current location, but can't mimic this on launch. The map initialises after the location manager, however, there is a delay in calling didUpdateLocations. This means it happens after the map has finished initialising, so initialiseMapCamera() defaults to coordinates of 0,0. If the correct perms are not in place, the map camera defaults to 1,1, as expected.

I'm sure there's an obvious way to fix this that I'm not thinking of; any help greatly appreciated. Thanks!

LocationManager - instantiated first

class LocationManager: NSObject, ObservableObject {
  
  private let locationManager = CLLocationManager()
  
  @Published var authorizationStatus: CLAuthorizationStatus = .notDetermined {
    willSet { objectWillChange.send() }
  }
  
  @Published var location: CLLocation? {
    willSet { objectWillChange.send() }
  }
  
  var latitude: CLLocationDegrees {
    return location?.coordinate.latitude ?? 0
  }
  
  var longitude: CLLocationDegrees {
    return location?.coordinate.longitude ?? 0
  }
  
  override init() {
    super.init()
    
    locationManager.delegate = self
    
    switch authorizationStatus {
    case .authorizedAlways, .authorizedWhenInUse:
      configureLocationSettings()
    case .notDetermined, .restricted, .denied: break
    @unknown default: fatalError()
    }
  }
  
  func configureLocationSettings() {
    locationManager.desiredAccuracy = kCLLocationAccuracyBest
    locationManager.startUpdatingLocation()
  }
  
  func requestLocationPermissions() {
    locationManager.requestWhenInUseAuthorization()
  }
}

extension LocationManager: CLLocationManagerDelegate {

  func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    guard let location = locations.last else { return }
    self.location = location
  }
  
  func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
    self.authorizationStatus = manager.authorizationStatus

    switch authorizationStatus {
    case .authorizedAlways, .authorizedWhenInUse:
      configureLocationSettings()
    case .notDetermined, .restricted, .denied: return
    @unknown default: 
      fatalError()
    }
  }
}

MapViewBridge - instantiated afterwards

struct MapViewControllerBridge: UIViewControllerRepresentable {
  
  @EnvironmentObject var locationManager: LocationManager
  @EnvironmentObject var mapViewModel: mapViewModel

  func makeUIViewController(context: Context) -> MapViewController {
    let uiViewController = MapViewController()
    initialiseMapCamera(mapView: uiViewController.mapView)
    return uiViewController
  }

  func updateUIViewController(_ uiViewController: MapViewController, context: Context) { 
    cameraToLocationButton(mapView: uiViewController.mapView)
  }
  
  private func initialiseMapCamera(mapView: GMSMapView) {
    var cameraLocation: CLLocationCoordinate2D
    
    switch locationManager.authorizationStatus {
    case .authorizedAlways, .authorizedWhenInUse:
      cameraLocation = CLLocationCoordinate2D(
        latitude: locationManager.latitude,
        longitude: locationManager.longitude)
    case .notDetermined, .restricted, .denied:
      cameraLocation = CLLocationCoordinate2D(latitude: 1.0, longitude: 1.0)
    @unknown default:
      fatalError()
    }
    mapView.camera = GMSCameraPosition.camera(withTarget: cameraLocation, zoom: defaultZoomLevel)
  }
  
  private func animateCameraToCurrentLocation(mapView: GMSMapView) {
    let currentLocation = CLLocationCoordinate2D(
      latitude: locationManager.latitude,
      longitude: locationManager.longitude)
    mapView.animate(with: GMSCameraUpdate.setTarget(currentLocation))
    mapView.animate(toZoom: defaultZoomLevel)
  }
  
  private func cameraToLocationButton(mapView: GMSMapView) {
    guard mapViewModel.cameraToLocation else {
      return
    }

    switch locationManager.authorizationStatus {
    case .authorizedWhenInUse, .authorizedAlways:
      animateCameraToCurrentLocation(mapView: mapView)
    case .notDetermined:
      locationManager.requestLocationPermissions()
    case .restricted, .denied:
      DispatchQueue.main.async {
        mapViewModel.showLocationSettingsAlert = true
      }
    @unknown default:
      fatalError()
    }
    
    DispatchQueue.main.async {
      mapViewModel.cameraToLocation = false
    }
  }
}
Angus
  • 133
  • 1
  • 8
  • 1
    You can't know the user's actual location until the first call to `didUpdateLocations`. What you can do is store the location when you get it in `UserDefaults`. That way the next time you launch you can move to the previous location, which is probably closer to where they are now, than the default – Paulw11 Jan 21 '22 at 22:30

1 Answers1

1

Create a loading view until the location is updated. Then once its updated disable the loading view and show the map and it will begin the camera movement on appearance of the map view.

Trevor
  • 580
  • 5
  • 16