Skip to content

Commit

Permalink
Merge branch 'main' into mapstyle-pois
Browse files Browse the repository at this point in the history
  • Loading branch information
hactar committed Jul 23, 2024
2 parents 6a9d74a + 49ccef3 commit 1e41063
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 26 deletions.
73 changes: 59 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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<NavigationViewController>(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

Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
@@ -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
}
Expand Down
10 changes: 3 additions & 7 deletions Sources/MapLibreSwiftUI/MapView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import MapLibre
import MapLibreSwiftDSL
import SwiftUI

public struct MapView<T: WrappedViewController>: UIViewControllerRepresentable {
public struct MapView<T: MapViewHostViewController>: UIViewControllerRepresentable {
public typealias UIViewControllerType = T
var cameraDisabled: Bool = true

Expand All @@ -20,10 +20,6 @@ public struct MapView<T: WrappedViewController>: 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] = [
Expand Down Expand Up @@ -131,7 +127,7 @@ public struct MapView<T: WrappedViewController>: UIViewControllerRepresentable {
}
}

public extension MapView where T == MapViewController {
public extension MapView where T == MLNMapViewController {
@MainActor
init(
styleURL: URL,
Expand All @@ -140,7 +136,7 @@ public extension MapView where T == MapViewController {
@MapViewContentBuilder _ makeMapContent: () -> [StyleLayerDefinition] = { [] }
) {
makeViewController = {
MapViewController()
MLNMapViewController()
}
styleSource = .url(styleURL)
_camera = camera
Expand Down
2 changes: 1 addition & 1 deletion Sources/MapLibreSwiftUI/MapViewCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Foundation
import MapLibre
import MapLibreSwiftDSL

public class MapViewCoordinator<T: WrappedViewController>: NSObject, MLNMapViewDelegate {
public class MapViewCoordinator<T: MapViewHostViewController>: NSObject, MLNMapViewDelegate {
// This must be weak, the UIViewRepresentable owns the MLNMapView.
weak var mapView: MLNMapView?
var parent: MapView<T>
Expand Down
4 changes: 4 additions & 0 deletions Sources/MapLibreSwiftUI/MapViewModifiers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import XCTest

final class MapViewCoordinatorCameraTests: XCTestCase {
var maplibreMapView: MockMLNMapViewCameraUpdating!
var mapView: MapView<MapViewController>!
var coordinator: MapView<MapViewController>.Coordinator!
var mapView: MapView<MLNMapViewController>!
var coordinator: MapView<MLNMapViewController>.Coordinator!

@MainActor
override func setUp() async throws {
Expand Down

0 comments on commit 1e41063

Please sign in to comment.