Awesome
<p align="center"> <img src="https://maplibre.org/img/maplibre-logo-big.svg" alt="MapLibre Logo"> </p>MapLibreSwiftUI
Swift DSLs for MapLibre Native, a free open-source renderer for interactive vector maps, to enable better integration with SwiftUI and generally enable easier use of MapLibre.
NOTE: This package has migrated from Stadia Maps to the MapLibre organization 🎉
If you previously installed this package,
see the CHANGELOG
for steps to ensure Xcode stays happy.
This package is a reimagining of the MapLibre API with a modern DSLs for SwiftUI. The pre-1.0 status means only that we are not yet committed to API stability yet, since we care deeply about finding the best way to express things for SwfitUI users. The package is robust for the subset of the MapLibre iOS API that it supports. Any breaking API changes will be reflected in release notes.
Goals
- Primary: Make common use cases easy and make complicated ones possible
- Easy integration of MapLibre into a modern SwiftUI app
- Add markers, polylines and similar annotations
- Interaction with features through gestures
- Clustering (common use case that's rather difficult for first timers)
- Overlays
- Dynamic styling
- Camera control
- Turn-by-turn Navigation (see the showcase integrations below)
- Animation
- Prevent most common classes of mistakes that users make with the lower level APIs (ex: adding the same source twice)
- Deeper SwiftUI integration (ex: SwiftUI callout views)
Quick start
In a normal Xcode project
If you're building an app using an Xcode project,
the easiest way to add package dependencies is in the File menu.
Search for the package using the repository URL: https://github.com/maplibre/swiftui-dsl
.
In a Swift package
Add the following to the main dependencies section of your Package.swift
.
.package(url: "https://github.com/maplibre/swiftui-dsl", branch: "main"),
Then, for each target add either the DSL (for just the DSL) or both (for the SwiftUI view):
.product(name: "MapLibreSwiftDSL", package: "swiftui-dsl"),
.product(name: "MapLibreSwiftUI", package: "swiftui-dsl"),
Simple example: polyline rendering
Then, you can use it in a SwiftUI view body like this:
import MapLibre
import MapLibreSwiftDSL
import SwiftUI
import CoreLocation
struct PolylineMapView: View {
// You'll need a MapLibre Style for this to work.
// You can use https://demotiles.maplibre.org/style.json for basic testing.
// For a list of commercially supported tile providers, check out https://wiki.openstreetmap.org/wiki/Vector_tiles#Providers.
// These providers all have their own "house styles" as well as custom styling.
// You can create your own style or modify others (subject to license restrictions) using https://maplibre.org/maputnik/.
let styleURL: URL
// Just a list of waypoints (ex: a route to follow)
let waypoints: [CLLocationCoordinate2D]
var body: some View {
MapView(styleURL: styleURL,
camera: .constant(.center(waypoints.first!, zoom: 14)))
{
// Define a data source.
// It will be automatically if a layer references it.
let polylineSource = ShapeSource(identifier: "polyline") {
MLNPolylineFeature(coordinates: waypoints)
}
// Add a polyline casing for a stroke effect
LineStyleLayer(identifier: "polyline-casing", source: polylineSource)
.lineCap(.round)
.lineJoin(.round)
.lineColor(.white)
.lineWidth(interpolatedBy: .zoomLevel,
curveType: .exponential,
parameters: NSExpression(forConstantValue: 1.5),
stops: NSExpression(forConstantValue: [14: 6, 18: 24]))
// Add an inner (blue) polyline
LineStyleLayer(identifier: "polyline-inner", source: polylineSource)
.lineCap(.round)
.lineJoin(.round)
.lineColor(.systemBlue)
.lineWidth(interpolatedBy: .zoomLevel,
curveType: .exponential,
parameters: NSExpression(forConstantValue: 1.5),
stops: NSExpression(forConstantValue: [14: 3, 18: 16]))
}
}
}
Check out more Examples to go deeper.
NOTE: This currently only works on iOS, as the dynamic framework doesn't yet include macOS.
How can you help?
The first thing you can do is try it out! Check out the Examples for inspiration, swap it into your own SwiftUI app, or check out some showcase integrations for inspiration. Putting it "through the paces" is the best way for us to converge on the "right" APIs as a community. Your use case probably isn't supported today, in which case you can either open an issue or contribute a PR.
The code has a number of TODOs, most of which can be tackled by any intermediate Swift programmer.
The important issues should all be tracked in GitHub.
We also have a #maplibre-swiftui-compose-playground
channel in the
OpenStreetMap US Slack.
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 and just start filling out the remaining properties and modifiers.
Showcase integrations
Ferrostar
Ferrostar has a MapLibre UI module as part of its Swift Package. That was actually the impetus for building this package, and the core devs are eating their own dogfood. See the SwiftUI customization part of the Ferrostar user guide for details on how to customize the map.
MapLibre Navigation iOS
This package also helps to bridge the gap between MapLibre Navigation iOS and SwiftUI! Thanks to developers from HudHud for their contributions which made this possible!
Add the Swift Package to your project. Then add some code like this:
import MapboxCoreNavigation
import MapboxNavigation
import MapLibreSwiftUI
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) {
// TODO: Your customizations here; add more layers or whatever you like!
}
.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)
}