From 3f8cdf4d7a5fbcbdfd8614408666992a44c7dd66 Mon Sep 17 00:00:00 2001 From: Bryan VonCannon Date: Tue, 21 May 2024 08:26:01 -0400 Subject: [PATCH] Improvements & Fixes: User Location and Location Privacy Example iOS (#2393) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../MapLibre.docc/LocationPrivacyExample.md | 96 ++++++++++--------- .../Sources/LocationPrivacyExample.swift | 75 +++++++-------- 2 files changed, 89 insertions(+), 82 deletions(-) diff --git a/platform/ios/MapLibre.docc/LocationPrivacyExample.md b/platform/ios/MapLibre.docc/LocationPrivacyExample.md index 65711794e..f78d403b3 100644 --- a/platform/ios/MapLibre.docc/LocationPrivacyExample.md +++ b/platform/ios/MapLibre.docc/LocationPrivacyExample.md @@ -6,14 +6,16 @@ Requesting precise location with ``MLNLocationManager``. This example shows how to request a precise location with ``MLNLocationManager``. -First of all you need to prepare your `Info.plist`. You need to provide a description why your app needs to access location: +Let's prepare your `Info.plist`: + +First, provide a description why your app needs to access location: ```plist NSLocationWhenInUseUsageDescription Dummy Location When In Use Description ``` -As well as a description why your app needs precise location. +Second, add a description why your app needs precise location: ```plist NSLocationTemporaryUsageDescriptionDictionary @@ -23,6 +25,13 @@ As well as a description why your app needs precise location. ``` +Third and finally, specify your app only needs reduced location access by default (until you request precise accuracy in the example): + +```plist +NSLocationDefaultAccuracyReduced + +``` + The `Coordinator` defined below is also the ``MLNMapViewDelegate``. When the location manager authorization changes it will call the relevant method. If precise location has not been granted, a button is shown at the bottom of the map. ![](ImpreciseLocation.png) @@ -40,75 +49,85 @@ enum LocationAccuracyState { case fullAccuracy } -class Coordinator: NSObject, MLNMapViewDelegate { - @Binding var mapView: MLNMapView - @Binding var locationAccuracy: LocationAccuracyState - var pannedToUserLocation = false +@MainActor +class PrivacyExampleViewModel: NSObject, ObservableObject { + @Published var locationAccuracy: LocationAccuracyState = .unknown + @Published var showTemporaryLocationAuthorization = false +} - init(mapView: Binding, locationAccuracy: Binding) { - _mapView = mapView - _locationAccuracy = locationAccuracy +class PrivacyExampleCoordinator: NSObject, MLNMapViewDelegate { + @ObservedObject private var mapViewModel: PrivacyExampleViewModel + private var pannedToUserLocation = false + + init(mapViewModel: PrivacyExampleViewModel) { + self.mapViewModel = mapViewModel + super.init() } - - func mapView(_: MLNMapView, didChangeLocationManagerAuthorization manager: MLNLocationManager) { + + @MainActor func mapView(_: MLNMapView, didChangeLocationManagerAuthorization manager: MLNLocationManager) { guard let accuracySetting = manager.accuracyAuthorization else { return } - + switch accuracySetting() { case .fullAccuracy: - locationAccuracy = .fullAccuracy + mapViewModel.locationAccuracy = .fullAccuracy case .reducedAccuracy: - locationAccuracy = .reducedAccuracy + mapViewModel.locationAccuracy = .reducedAccuracy @unknown default: - locationAccuracy = .unknown + mapViewModel.locationAccuracy = .unknown } } // when a location is available for the first time, we fly to it func mapView(_ mapView: MLNMapView, didUpdate _: MLNUserLocation?) { - if pannedToUserLocation { - return - } + guard !pannedToUserLocation else { return } guard let userLocation = mapView.userLocation else { print("User location is currently not available.") return } mapView.fly(to: MLNMapCamera(lookingAtCenter: userLocation.coordinate, altitude: 100_000, pitch: 0, heading: 0)) + pannedToUserLocation = true } } -struct LocationPrivacyExample: UIViewRepresentable { - @Binding var mapView: MLNMapView - @Binding var locationAccuracy: LocationAccuracyState - - func makeCoordinator() -> Coordinator { - Coordinator(mapView: $mapView, locationAccuracy: $locationAccuracy) +struct PrivacyExampleRepresentable: UIViewRepresentable { + @ObservedObject var mapViewModel: PrivacyExampleViewModel + + func makeCoordinator() -> PrivacyExampleCoordinator { + PrivacyExampleCoordinator(mapViewModel: mapViewModel) } func makeUIView(context: Context) -> MLNMapView { let mapView = MLNMapView() - mapView.showsUserLocation = true + mapView.delegate = context.coordinator - + mapView.showsUserLocation = true return mapView } - func updateUIView(_: MLNMapView, context _: Context) {} + func updateUIView(_ mapView: MLNMapView, context _: Context) { + if mapViewModel.showTemporaryLocationAuthorization { + let purposeKey = "MLNAccuracyAuthorizationDescription" + mapView.locationManager.requestTemporaryFullAccuracyAuthorization?(withPurposeKey: purposeKey) + DispatchQueue.main.async { + mapViewModel.showTemporaryLocationAuthorization = false + } + } + } } struct LocationPrivacyExampleView: View { - @State private var mapView = MLNMapView() - @State var locationAccuracy: LocationAccuracyState = .unknown - + @StateObject private var viewModel = PrivacyExampleViewModel() + var body: some View { VStack { - LocationPrivacyExample(mapView: $mapView, locationAccuracy: $locationAccuracy) + PrivacyExampleRepresentable(mapViewModel: viewModel) .edgesIgnoringSafeArea(.all) - if locationAccuracy == LocationAccuracyState.reducedAccuracy { + if viewModel.locationAccuracy == LocationAccuracyState.reducedAccuracy { Button("Request Precise Location") { - handleButtonPress(mapView: mapView) + viewModel.showTemporaryLocationAuthorization.toggle() } .padding() .background(Color.blue) @@ -117,16 +136,5 @@ struct LocationPrivacyExampleView: View { } } } - - private func handleButtonPress(mapView: MLNMapView) { - print("Requesting precice location") - switch locationAccuracy { - case .reducedAccuracy: - let purposeKey = "MLNAccuracyAuthorizationDescription" - mapView.locationManager.requestTemporaryFullAccuracyAuthorization!(withPurposeKey: purposeKey) - default: - break - } - } } ``` \ No newline at end of file diff --git a/platform/ios/app-swift/Sources/LocationPrivacyExample.swift b/platform/ios/app-swift/Sources/LocationPrivacyExample.swift index 8b65a5c05..481bc8985 100644 --- a/platform/ios/app-swift/Sources/LocationPrivacyExample.swift +++ b/platform/ios/app-swift/Sources/LocationPrivacyExample.swift @@ -9,75 +9,85 @@ enum LocationAccuracyState { case fullAccuracy } -class Coordinator: NSObject, MLNMapViewDelegate { - @Binding var mapView: MLNMapView - @Binding var locationAccuracy: LocationAccuracyState - var pannedToUserLocation = false +@MainActor +class PrivacyExampleViewModel: NSObject, ObservableObject { + @Published var locationAccuracy: LocationAccuracyState = .unknown + @Published var showTemporaryLocationAuthorization = false +} - init(mapView: Binding, locationAccuracy: Binding) { - _mapView = mapView - _locationAccuracy = locationAccuracy +class PrivacyExampleCoordinator: NSObject, MLNMapViewDelegate { + @ObservedObject private var mapViewModel: PrivacyExampleViewModel + private var pannedToUserLocation = false + + init(mapViewModel: PrivacyExampleViewModel) { + self.mapViewModel = mapViewModel + super.init() } - func mapView(_: MLNMapView, didChangeLocationManagerAuthorization manager: MLNLocationManager) { + @MainActor func mapView(_: MLNMapView, didChangeLocationManagerAuthorization manager: MLNLocationManager) { guard let accuracySetting = manager.accuracyAuthorization else { return } switch accuracySetting() { case .fullAccuracy: - locationAccuracy = .fullAccuracy + mapViewModel.locationAccuracy = .fullAccuracy case .reducedAccuracy: - locationAccuracy = .reducedAccuracy + mapViewModel.locationAccuracy = .reducedAccuracy @unknown default: - locationAccuracy = .unknown + mapViewModel.locationAccuracy = .unknown } } // when a location is available for the first time, we fly to it func mapView(_ mapView: MLNMapView, didUpdate _: MLNUserLocation?) { - if pannedToUserLocation { - return - } + guard !pannedToUserLocation else { return } guard let userLocation = mapView.userLocation else { print("User location is currently not available.") return } mapView.fly(to: MLNMapCamera(lookingAtCenter: userLocation.coordinate, altitude: 100_000, pitch: 0, heading: 0)) + pannedToUserLocation = true } } -struct LocationPrivacyExample: UIViewRepresentable { - @Binding var mapView: MLNMapView - @Binding var locationAccuracy: LocationAccuracyState +struct PrivacyExampleRepresentable: UIViewRepresentable { + @ObservedObject var mapViewModel: PrivacyExampleViewModel - func makeCoordinator() -> Coordinator { - Coordinator(mapView: $mapView, locationAccuracy: $locationAccuracy) + func makeCoordinator() -> PrivacyExampleCoordinator { + PrivacyExampleCoordinator(mapViewModel: mapViewModel) } func makeUIView(context: Context) -> MLNMapView { let mapView = MLNMapView() - mapView.showsUserLocation = true - mapView.delegate = context.coordinator + mapView.delegate = context.coordinator + mapView.showsUserLocation = true return mapView } - func updateUIView(_: MLNMapView, context _: Context) {} + func updateUIView(_ mapView: MLNMapView, context _: Context) { + if mapViewModel.showTemporaryLocationAuthorization { + let purposeKey = "MLNAccuracyAuthorizationDescription" + mapView.locationManager.requestTemporaryFullAccuracyAuthorization?(withPurposeKey: purposeKey) + DispatchQueue.main.async { + mapViewModel.showTemporaryLocationAuthorization = false + } + } + } } struct LocationPrivacyExampleView: View { - @State private var mapView = MLNMapView() - @State var locationAccuracy: LocationAccuracyState = .unknown + @StateObject private var viewModel = PrivacyExampleViewModel() var body: some View { VStack { - LocationPrivacyExample(mapView: $mapView, locationAccuracy: $locationAccuracy) + PrivacyExampleRepresentable(mapViewModel: viewModel) .edgesIgnoringSafeArea(.all) - if locationAccuracy == LocationAccuracyState.reducedAccuracy { + if viewModel.locationAccuracy == LocationAccuracyState.reducedAccuracy { Button("Request Precise Location") { - handleButtonPress(mapView: mapView) + viewModel.showTemporaryLocationAuthorization.toggle() } .padding() .background(Color.blue) @@ -86,17 +96,6 @@ struct LocationPrivacyExampleView: View { } } } - - private func handleButtonPress(mapView: MLNMapView) { - print("Requesting precice location") - switch locationAccuracy { - case .reducedAccuracy: - let purposeKey = "MLNAccuracyAuthorizationDescription" - mapView.locationManager.requestTemporaryFullAccuracyAuthorization!(withPurposeKey: purposeKey) - default: - break - } - } } // #-end-example-code