diff --git a/platform/ios/CONTRIBUTING.md b/platform/ios/CONTRIBUTING.md
index 55ac9c744..1f4d1bafa 100644
--- a/platform/ios/CONTRIBUTING.md
+++ b/platform/ios/CONTRIBUTING.md
@@ -35,7 +35,7 @@ The code samples in the documentation should ideally be compiled on CI so they d
Fence your example code with
```swift
-// #-example-code(LineTapMapView)
+// #-example-code(LineTapMap)
...
// #-end-example-code
```
@@ -43,7 +43,7 @@ Fence your example code with
Prefix your documentation code block with
````md
-
+
```swift
...
diff --git a/platform/ios/MapLibre.docc/GettingStarted.md b/platform/ios/MapLibre.docc/GettingStarted.md
index be5a8e1ac..72538f892 100644
--- a/platform/ios/MapLibre.docc/GettingStarted.md
+++ b/platform/ios/MapLibre.docc/GettingStarted.md
@@ -30,10 +30,10 @@ import MapLibre
To use MapLibre with SwiftUI we need to create a wrapper for the UIKit view that MapLibre provides (using UIViewRepresentable. The simplest way to implement this protocol is as follows:
-
+
```swift
-struct SimpleMapView: UIViewRepresentable {
+struct SimpleMap: UIViewRepresentable {
func makeUIView(context _: Context) -> MLNMapView {
let mapView = MLNMapView()
return mapView
diff --git a/platform/ios/MapLibre.docc/LineOnUserTap.md b/platform/ios/MapLibre.docc/LineOnUserTap.md
index e7d3dbe83..5dca589a9 100644
--- a/platform/ios/MapLibre.docc/LineOnUserTap.md
+++ b/platform/ios/MapLibre.docc/LineOnUserTap.md
@@ -6,10 +6,10 @@ Demonstrating adding ``MLNPolyline`` annotations and responding to user input.
This example draws a line from the tapped location to the center of the map. Handling the tap is done by the `Coordinator` class. It converts the location on the view to a geographic coordinate. It removes existing annotations before adding the new line.
-
+
```swift
-struct LineTapMapView: UIViewRepresentable {
+struct LineTapMap: UIViewRepresentable {
func makeUIView(context: Context) -> MLNMapView {
let mapView = MLNMapView()
@@ -32,9 +32,9 @@ struct LineTapMapView: UIViewRepresentable {
}
class Coordinator: NSObject {
- var parent: LineTapMapView
+ var parent: LineTapMap
- init(_ parent: LineTapMapView) {
+ init(_ parent: LineTapMap) {
self.parent = parent
}
diff --git a/platform/ios/MapLibre.docc/LocationPrivacyExample.md b/platform/ios/MapLibre.docc/LocationPrivacyExample.md
new file mode 100644
index 000000000..65711794e
--- /dev/null
+++ b/platform/ios/MapLibre.docc/LocationPrivacyExample.md
@@ -0,0 +1,132 @@
+# User Location & Location Privacy
+
+Requesting precise location with ``MLNLocationManager``.
+
+## Overview
+
+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:
+
+```plist
+NSLocationWhenInUseUsageDescription
+Dummy Location When In Use Description
+```
+
+As well as a description why your app needs precise location.
+
+```plist
+NSLocationTemporaryUsageDescriptionDictionary
+
+ MLNAccuracyAuthorizationDescription
+ Dummy Precise Location Description
+
+```
+
+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.
+
+
+
+When the button is pressed a pop-up with the description we set in `Info.plist` will be shown:
+
+
+
+
+
+```swift
+enum LocationAccuracyState {
+ case unknown
+ case reducedAccuracy
+ case fullAccuracy
+}
+
+class Coordinator: NSObject, MLNMapViewDelegate {
+ @Binding var mapView: MLNMapView
+ @Binding var locationAccuracy: LocationAccuracyState
+ var pannedToUserLocation = false
+
+ init(mapView: Binding, locationAccuracy: Binding) {
+ _mapView = mapView
+ _locationAccuracy = locationAccuracy
+ }
+
+ func mapView(_: MLNMapView, didChangeLocationManagerAuthorization manager: MLNLocationManager) {
+ guard let accuracySetting = manager.accuracyAuthorization else {
+ return
+ }
+
+ switch accuracySetting() {
+ case .fullAccuracy:
+ locationAccuracy = .fullAccuracy
+ case .reducedAccuracy:
+ locationAccuracy = .reducedAccuracy
+ @unknown default:
+ 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 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))
+ }
+}
+
+struct LocationPrivacyExample: UIViewRepresentable {
+ @Binding var mapView: MLNMapView
+ @Binding var locationAccuracy: LocationAccuracyState
+
+ func makeCoordinator() -> Coordinator {
+ Coordinator(mapView: $mapView, locationAccuracy: $locationAccuracy)
+ }
+
+ func makeUIView(context: Context) -> MLNMapView {
+ let mapView = MLNMapView()
+ mapView.showsUserLocation = true
+ mapView.delegate = context.coordinator
+
+ return mapView
+ }
+
+ func updateUIView(_: MLNMapView, context _: Context) {}
+}
+
+struct LocationPrivacyExampleView: View {
+ @State private var mapView = MLNMapView()
+ @State var locationAccuracy: LocationAccuracyState = .unknown
+
+ var body: some View {
+ VStack {
+ LocationPrivacyExample(mapView: $mapView, locationAccuracy: $locationAccuracy)
+ .edgesIgnoringSafeArea(.all)
+
+ if locationAccuracy == LocationAccuracyState.reducedAccuracy {
+ Button("Request Precise Location") {
+ handleButtonPress(mapView: mapView)
+ }
+ .padding()
+ .background(Color.blue)
+ .foregroundColor(.white)
+ .cornerRadius(8)
+ }
+ }
+ }
+
+ 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/MapLibre.docc/MapLibre.md b/platform/ios/MapLibre.docc/MapLibre.md
index 51a797419..c1595de3f 100644
--- a/platform/ios/MapLibre.docc/MapLibre.md
+++ b/platform/ios/MapLibre.docc/MapLibre.md
@@ -15,6 +15,7 @@ Powerful, free and open-source mapping toolkit with full control over data sourc
-
-
+-
## Topics
diff --git a/platform/ios/app-swift/BUILD.bazel b/platform/ios/app-swift/BUILD.bazel
index 9ecadbda9..92c4999c2 100644
--- a/platform/ios/app-swift/BUILD.bazel
+++ b/platform/ios/app-swift/BUILD.bazel
@@ -25,7 +25,7 @@ ios_application(
"ipad",
],
infoplists = [":Info.plist"],
- minimum_os_version = "15.0",
+ minimum_os_version = "16.0",
provisioning_profile = "xcode_profile",
visibility = ["@rules_xcodeproj//xcodeproj:generated"],
deps = [":Sources"],
diff --git a/platform/ios/app-swift/Info.plist b/platform/ios/app-swift/Info.plist
index 95fdf06b7..9ece1a40c 100644
--- a/platform/ios/app-swift/Info.plist
+++ b/platform/ios/app-swift/Info.plist
@@ -2,6 +2,15 @@
+ NSLocationTemporaryUsageDescriptionDictionary
+
+ MLNAccuracyAuthorizationDescription
+ Dummy Precise Location Description
+
+ LSApplicationCategoryType
+
+ NSLocationWhenInUseUsageDescription
+ Dummy Location When In Use Description
UILaunchScreen
UILaunchScreen
@@ -36,4 +45,4 @@
UIInterfaceOrientationLandscapeRight
-
\ No newline at end of file
+
diff --git a/platform/ios/app-swift/Sources/LineTapMapView.swift b/platform/ios/app-swift/Sources/LineTapMapView.swift
index c53df936a..daaeb0d80 100644
--- a/platform/ios/app-swift/Sources/LineTapMapView.swift
+++ b/platform/ios/app-swift/Sources/LineTapMapView.swift
@@ -2,8 +2,8 @@ import MapLibre
import SwiftUI
import UIKit
-// #-example-code(LineTapMapView)
-struct LineTapMapView: UIViewRepresentable {
+// #-example-code(LineTapMap)
+struct LineTapMap: UIViewRepresentable {
func makeUIView(context: Context) -> MLNMapView {
let mapView = MLNMapView()
@@ -26,9 +26,9 @@ struct LineTapMapView: UIViewRepresentable {
}
class Coordinator: NSObject {
- var parent: LineTapMapView
+ var parent: LineTapMap
- init(_ parent: LineTapMapView) {
+ init(_ parent: LineTapMap) {
self.parent = parent
}
diff --git a/platform/ios/app-swift/Sources/LocationPrivacyExample.swift b/platform/ios/app-swift/Sources/LocationPrivacyExample.swift
new file mode 100644
index 000000000..8b65a5c05
--- /dev/null
+++ b/platform/ios/app-swift/Sources/LocationPrivacyExample.swift
@@ -0,0 +1,102 @@
+import MapLibre
+import SwiftUI
+import UIKit
+
+// #-example-code(LocationPrivacyExample)
+enum LocationAccuracyState {
+ case unknown
+ case reducedAccuracy
+ case fullAccuracy
+}
+
+class Coordinator: NSObject, MLNMapViewDelegate {
+ @Binding var mapView: MLNMapView
+ @Binding var locationAccuracy: LocationAccuracyState
+ var pannedToUserLocation = false
+
+ init(mapView: Binding, locationAccuracy: Binding) {
+ _mapView = mapView
+ _locationAccuracy = locationAccuracy
+ }
+
+ func mapView(_: MLNMapView, didChangeLocationManagerAuthorization manager: MLNLocationManager) {
+ guard let accuracySetting = manager.accuracyAuthorization else {
+ return
+ }
+
+ switch accuracySetting() {
+ case .fullAccuracy:
+ locationAccuracy = .fullAccuracy
+ case .reducedAccuracy:
+ locationAccuracy = .reducedAccuracy
+ @unknown default:
+ 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 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))
+ }
+}
+
+struct LocationPrivacyExample: UIViewRepresentable {
+ @Binding var mapView: MLNMapView
+ @Binding var locationAccuracy: LocationAccuracyState
+
+ func makeCoordinator() -> Coordinator {
+ Coordinator(mapView: $mapView, locationAccuracy: $locationAccuracy)
+ }
+
+ func makeUIView(context: Context) -> MLNMapView {
+ let mapView = MLNMapView()
+ mapView.showsUserLocation = true
+ mapView.delegate = context.coordinator
+
+ return mapView
+ }
+
+ func updateUIView(_: MLNMapView, context _: Context) {}
+}
+
+struct LocationPrivacyExampleView: View {
+ @State private var mapView = MLNMapView()
+ @State var locationAccuracy: LocationAccuracyState = .unknown
+
+ var body: some View {
+ VStack {
+ LocationPrivacyExample(mapView: $mapView, locationAccuracy: $locationAccuracy)
+ .edgesIgnoringSafeArea(.all)
+
+ if locationAccuracy == LocationAccuracyState.reducedAccuracy {
+ Button("Request Precise Location") {
+ handleButtonPress(mapView: mapView)
+ }
+ .padding()
+ .background(Color.blue)
+ .foregroundColor(.white)
+ .cornerRadius(8)
+ }
+ }
+ }
+
+ 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
diff --git a/platform/ios/app-swift/Sources/MapLibreApp.swift b/platform/ios/app-swift/Sources/MapLibreApp.swift
index 69bd591de..85c8caa63 100644
--- a/platform/ios/app-swift/Sources/MapLibreApp.swift
+++ b/platform/ios/app-swift/Sources/MapLibreApp.swift
@@ -6,7 +6,7 @@ import UIKit
struct MapLibreApp: App {
var body: some Scene {
WindowGroup {
- ContentView()
+ MapLibreNavigationView()
}
}
}
diff --git a/platform/ios/app-swift/Sources/MapLibreNavigationView.swift b/platform/ios/app-swift/Sources/MapLibreNavigationView.swift
new file mode 100644
index 000000000..8559de588
--- /dev/null
+++ b/platform/ios/app-swift/Sources/MapLibreNavigationView.swift
@@ -0,0 +1,20 @@
+import MapLibre
+import SwiftUI
+
+struct MapLibreNavigationView: View {
+ var body: some View {
+ NavigationStack {
+ List {
+ NavigationLink("SimpleMap") {
+ SimpleMap().edgesIgnoringSafeArea(.all)
+ }
+ NavigationLink("LineTapMap") {
+ LineTapMap().edgesIgnoringSafeArea(.all)
+ }
+ NavigationLink("LocationPrivacyExample") {
+ LocationPrivacyExampleView()
+ }
+ }
+ }
+ }
+}
diff --git a/platform/ios/app-swift/Sources/SimpleMapView.swift b/platform/ios/app-swift/Sources/SimpleMapView.swift
index 111df59a0..893157a8e 100644
--- a/platform/ios/app-swift/Sources/SimpleMapView.swift
+++ b/platform/ios/app-swift/Sources/SimpleMapView.swift
@@ -2,8 +2,8 @@ import MapLibre
import SwiftUI
import UIKit
-// #-example-code(SimpleMapView)
-struct SimpleMapView: UIViewRepresentable {
+// #-example-code(SimpleMap)
+struct SimpleMap: UIViewRepresentable {
func makeUIView(context _: Context) -> MLNMapView {
let mapView = MLNMapView()
return mapView
@@ -13,9 +13,3 @@ struct SimpleMapView: UIViewRepresentable {
}
// #-end-example-code
-
-struct ContentView: View {
- var body: some View {
- SimpleMapView().edgesIgnoringSafeArea(.all)
- }
-}
diff --git a/scripts/update-ios-examples.mjs b/scripts/update-ios-examples.mjs
index 105fdb8a4..26dbb43c3 100644
--- a/scripts/update-ios-examples.mjs
+++ b/scripts/update-ios-examples.mjs
@@ -4,18 +4,23 @@
*/
import fs from "fs/promises";
+import { join } from "path";
-/** documentation files that will be updated */
-const documentationPaths = [
- "platform/ios/MapLibre.docc/GettingStarted.md",
- "platform/ios/MapLibre.docc/LineOnUserTap.md",
-];
+/**
+ * @param {string} baseDir
+ * @param {(path: string) => boolean} predicate
+ * @returns paths
+ */
+async function getFilePaths(baseDir, predicate) {
+ const entries = (await fs.readdir(baseDir, { withFileTypes: true }))
+ return entries.map((entry) => join(entry.path, entry.name)).filter(predicate);
+}
-/** source files that contain examples */
-const examplePaths = [
- "platform/ios/app-swift/Sources/SimpleMapView.swift",
- "platform/ios/app-swift/Sources/LineTapMapView.swift"
-];
+// documentation files that will be updated and source files that contain examples
+const [documentationPaths, examplePaths] = await Promise.all([
+ getFilePaths("platform/ios/MapLibre.docc", path => path.endsWith(".md")),
+ getFilePaths("platform/ios/app-swift/Sources", path => path.endsWith(".swift"))
+]);
/**
* Parses a source file and looks for examples fenced in
@@ -67,7 +72,8 @@ async function loadExamples() {
for (const examplePath of examplePaths) {
const newExamples = await parseExampleCode(examplePath);
if (Object.keys(newExamples).length === 0) {
- throw new Error(`File '${examplePath}' does not contain any examples`);
+ // no examples found
+ continue;
}
examples = {...newExamples, ...examples};
}