diff --git a/README.md b/README.md index 4c03021..88ba675 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ can move fast without breaking anything important. * Overlays * Dynamic styling * Camera control / animation?? + * Navigation 2. Prevent most common classes of mistakes that users make with the lower level APIs (ex: adding the same source twice) 3. Deeper SwiftUI integration (ex: SwiftUI callout views) @@ -46,19 +47,73 @@ Then, for each target add either the DSL (for just the DSL) or both (for the Swi Check out the (super basic) [previews at the bottom of MapView.swift](Sources/MapLibreSwiftUI/MapView.swift) or more detailed [Examples](Sources/MapLibreSwiftUI/Examples) to see how it works in practice. +## Common integrations + +### Ferrostar + +[Ferrostar](https://github.com/stadiamaps/ferrostar) has a MapLibre UI module as part of its Swift Package +which is already built on this! +See the [SwiftUI customization](https://stadiamaps.github.io/ferrostar/swiftui-customization.html) +part of the Ferrostar user guide for details on how to customize the map. + +### MapLibre Navigation iOS + +This package can help bridge the gap between MapLibre Navigation iOS and SwiftUI! + +Add the Swift Package froh Hudhud (https://github.com/HudHud-Maps/maplibre-navigation-ios.git) to your Package.swift +and add code like this: + +```swift +import MapboxCoreNavigation +import MapboxNavigation + +extension NavigationViewController: MapViewHostViewController { + public typealias MapType = NavigationMapView +} + + +@State var route: Route? +@State var navigationInProgress: Bool = false + +@ViewBuilder +var mapView: some View { + MapView(makeViewController: NavigationViewController(dayStyleURL: self.styleURL), styleURL: self.styleURL, camera: self.$mapStore.camera) { + + } + .unsafeMapViewControllerModifier { navigationViewController in + navigationViewController.delegate = self.mapStore + if let route = self.route, self.navigationInProgress == false { + let locationManager = SimulatedLocationManager(route: route) + navigationViewController.startNavigation(with: route, locationManager: locationManager) + self.navigationInProgress = true + } else if self.route == nil, self.navigationInProgress == true { + navigationViewController.endNavigation() + self.navigationInProgress = false + } + + navigationViewController.mapView.showsUserLocation = self.showUserLocation && self.mapStore.streetView == .disabled + } + .cameraModifierDisabled(self.route != nil) +} +``` + ## Developer Quick Start -This project uses [`swiftformat`](https://github.com/nicklockwood/SwiftFormat) to automatically handle basic swift formatting -as well as to lint and ensure conformance in PRs. Check out the swiftformat [Install Guide](https://github.com/nicklockwood/SwiftFormat?tab=readme-ov-file#how-do-i-install-it) -to add swiftformat to your machine. +This project is a standard Swift package. +The only special thing you might need besides Xcode is [`swiftformat`](https://github.com/nicklockwood/SwiftFormat). +We use it to automatically handle basic formatting and to linting +so the code has a standard style. +Check out the swiftformat [Install Guide](https://github.com/nicklockwood/SwiftFormat?tab=readme-ov-file#how-do-i-install-it) +to add swiftformat to your machine. Once installed, you can autoformat code using the command: ```sh swiftformat . ``` -Swiftformat can occasionally poorly resolve a formatting issue (e.g. when you've already line-broken a large comment). Issues like this are typically easy to manually correct. +Swiftformat can occasionally poorly resolve a formatting issue (e.g. when you've already line-broken a large comment). +Issues like this are typically easy to manually correct. ## Structure @@ -86,17 +141,7 @@ The code has a number of TODOs, most of which can be tackled by any intermediate issues should all be tracked in GitHub. DISCUSS comments (should migrate to GitHub issues/discussions) are deeper questions. Input welcome. -WARNING: This section is slightly out of date. We are targeting MapLibre 6.0 pre-releases, so the docs are not totally -up to date. - The skeleton is already in place for several of the core concepts, including style layers and sources, but these are incomplete. You can help by opening a PR that fills these in. For example, if you wanted to fill out the API for the line style layer, head over to [the docs](https://maplibre.org/maplibre-native/ios/api/Classes/MGLLineStyleLayer.html) and get to work filling out the remaining properties and modifiers. - -Note that some Swift 5.9 improvements like macros will make things a bit easier, so it may make sense to punt on -things like fleshing out the expressions API helpers (which will be highly repetitive) till that lands. When in doubt, -check out the issue tracker, as these sholud be noted there. - -TODO: Look at PointFree's Snapshot testing that generates images of SwiftUI views. Also look at inline snapshotting. -https://github.com/pointfreeco/swift-snapshot-testing \ No newline at end of file diff --git a/Sources/MapLibreSwiftUI/MapViewController.swift b/Sources/MapLibreSwiftUI/MLNMapViewController.swift similarity index 63% rename from Sources/MapLibreSwiftUI/MapViewController.swift rename to Sources/MapLibreSwiftUI/MLNMapViewController.swift index c07ae72..65f3903 100644 --- a/Sources/MapLibreSwiftUI/MapViewController.swift +++ b/Sources/MapLibreSwiftUI/MLNMapViewController.swift @@ -1,12 +1,12 @@ import MapLibre import UIKit -public protocol WrappedViewController: UIViewController { +public protocol MapViewHostViewController: UIViewController { associatedtype MapType: MLNMapView var mapView: MapType { get } } -public final class MapViewController: UIViewController, WrappedViewController { +public final class MLNMapViewController: UIViewController, MapViewHostViewController { public var mapView: MLNMapView { view as! MLNMapView } diff --git a/Sources/MapLibreSwiftUI/MapView.swift b/Sources/MapLibreSwiftUI/MapView.swift index 3e51f55..8ed40eb 100644 --- a/Sources/MapLibreSwiftUI/MapView.swift +++ b/Sources/MapLibreSwiftUI/MapView.swift @@ -3,7 +3,7 @@ import MapLibre import MapLibreSwiftDSL import SwiftUI -public struct MapView: UIViewControllerRepresentable { +public struct MapView: UIViewControllerRepresentable { public typealias UIViewControllerType = T var cameraDisabled: Bool = true @@ -20,10 +20,6 @@ public struct MapView: UIViewControllerRepresentable { public var mapViewContentInset: UIEdgeInsets = .zero - /// 'Escape hatch' to MLNMapView until we have more modifiers. - /// See ``unsafeMapViewModifier(_:)`` - var unsafeMapViewModifier: ((T.MapType) -> Void)? - var unsafeMapViewControllerModifier: ((T) -> Void)? var controls: [MapControl] = [ @@ -131,7 +127,7 @@ public struct MapView: UIViewControllerRepresentable { } } -public extension MapView where T == MapViewController { +public extension MapView where T == MLNMapViewController { @MainActor init( styleURL: URL, @@ -140,7 +136,7 @@ public extension MapView where T == MapViewController { @MapViewContentBuilder _ makeMapContent: () -> [StyleLayerDefinition] = { [] } ) { makeViewController = { - MapViewController() + MLNMapViewController() } styleSource = .url(styleURL) _camera = camera diff --git a/Sources/MapLibreSwiftUI/MapViewCoordinator.swift b/Sources/MapLibreSwiftUI/MapViewCoordinator.swift index 6c6d64b..52051df 100644 --- a/Sources/MapLibreSwiftUI/MapViewCoordinator.swift +++ b/Sources/MapLibreSwiftUI/MapViewCoordinator.swift @@ -2,7 +2,7 @@ import Foundation import MapLibre import MapLibreSwiftDSL -public class MapViewCoordinator: NSObject, MLNMapViewDelegate { +public class MapViewCoordinator: NSObject, MLNMapViewDelegate { // This must be weak, the UIViewRepresentable owns the MLNMapView. weak var mapView: MLNMapView? var parent: MapView diff --git a/Sources/MapLibreSwiftUI/MapViewModifiers.swift b/Sources/MapLibreSwiftUI/MapViewModifiers.swift index c0a1a23..29a309b 100644 --- a/Sources/MapLibreSwiftUI/MapViewModifiers.swift +++ b/Sources/MapLibreSwiftUI/MapViewModifiers.swift @@ -136,6 +136,10 @@ public extension MapView { return result } + /// Prevent Maplibre-DSL from updating the camera, useful when the underlying ViewController is managing the camera, + /// for example during navigation when Maplibre-Navigation is used. + /// - Parameter disabled: if true, prevents Maplibre-DSL from updating the camera + /// - Returns: The modified MapView func cameraModifierDisabled(_ disabled: Bool) -> Self { var view = self view.cameraDisabled = disabled diff --git a/Tests/MapLibreSwiftUITests/MapViewCoordinator/MapViewCoordinatorCameraTests.swift b/Tests/MapLibreSwiftUITests/MapViewCoordinator/MapViewCoordinatorCameraTests.swift index 3cbff2b..47efe30 100644 --- a/Tests/MapLibreSwiftUITests/MapViewCoordinator/MapViewCoordinatorCameraTests.swift +++ b/Tests/MapLibreSwiftUITests/MapViewCoordinator/MapViewCoordinatorCameraTests.swift @@ -5,8 +5,8 @@ import XCTest final class MapViewCoordinatorCameraTests: XCTestCase { var maplibreMapView: MockMLNMapViewCameraUpdating! - var mapView: MapView! - var coordinator: MapView.Coordinator! + var mapView: MapView! + var coordinator: MapView.Coordinator! @MainActor override func setUp() async throws {