Skip to content

Commit

Permalink
[MAPSIOS-1384] Style Imports API (#2115)
Browse files Browse the repository at this point in the history
  • Loading branch information
aleksproger committed Apr 22, 2024
1 parent af72618 commit 9a70af6
Show file tree
Hide file tree
Showing 27 changed files with 764 additions and 337 deletions.
Original file line number Diff line number Diff line change
@@ -1,18 +1,4 @@
{
"imports": [
{
"id": "standard",
"url": "mapbox://styles/mapbox/standard",
"config": {
"font": "Montserrat",
"lightPreset": "dusk",
"showPointOfInterestLabels": true,
"showTransitLabels": false,
"showPlaceLabels": true,
"showRoadLabels": false
}
}
],
"version": 8,
"sprite": "mapbox://sprites/examples/clkiip5x600b501pb4al5g7wv/du1h5vs55f48z8rgjod0dggl9",
"projection": { "name": "globe" },
Expand All @@ -31,26 +17,6 @@
}
},
"layers": [
{
"id": "water",
"type": "fill",
"slot": "bottom",
"source": "water_source",
"source-layer": "water",
"layout": { },
"paint": {
"fill-emissive-strength": 1,
"fill-color": [
"interpolate",
[ "linear" ],
[ "measure-light", "brightness" ],
0.1,
"hsl(250, 50%, 40%)",
0.4,
"hsl(250, 50%, 80%)"
]
}
},
{
"id": "subway-lines",
"type": "line",
Expand Down
63 changes: 43 additions & 20 deletions Apps/Examples/Examples/All Examples/StandardStyleExample.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,18 @@ final class StandardStyleExample: UIViewController, ExampleProtocol {
private var cancelables = Set<AnyCancelable>()
private var lightPreset = StandardLightPreset.night
private var labelsSetting = true
private var showRealEstate = true

// The fragment-realestate-NY.json style imports standard style with "standard" import id.
// Here we specify import config to that style.
private var mapStyle: MapStyle {
MapStyle(
uri: StyleURI(url: styleURL)!,
importConfigurations: [
.standard(
importId: "standard",
lightPreset: lightPreset,
showPointOfInterestLabels: labelsSetting,
showTransitLabels: labelsSetting,
showPlaceLabels: labelsSetting,
showRoadLabels: labelsSetting)
]
.standard(
lightPreset: lightPreset,
showPointOfInterestLabels: labelsSetting,
showTransitLabels: labelsSetting,
showPlaceLabels: labelsSetting,
showRoadLabels: labelsSetting
)
}

// Load a style which imports Mapbox Standard as a basemap
private let styleURL = Bundle.main.url(forResource: "fragment-realestate-NY", withExtension: "json")!

override func viewDidLoad() {
super.viewDidLoad()

Expand All @@ -39,6 +30,7 @@ final class StandardStyleExample: UIViewController, ExampleProtocol {

// When the style has finished loading add a line layer representing the border between New York and New Jersey
mapView.mapboxMap.onStyleLoaded.observe { [weak self] _ in
guard let self else { return }

// Create and apply basic styling to the line layer, assign the layer to the "middle" slot
var layer = LineLayer(id: "line-layer", source: "line-layer")
Expand All @@ -59,20 +51,23 @@ final class StandardStyleExample: UIViewController, ExampleProtocol {
])))

do {
try self?.mapView.mapboxMap.addSource(source)
try self?.mapView.mapboxMap.addLayer(layer)
try mapView.mapboxMap.addSource(source)
try mapView.mapboxMap.addLayer(layer)
} catch {
print(error)
}

toggleRealEstate(isOn: showRealEstate)

// The below line is used for internal testing purposes only.
self?.finish()
finish()
}.store(in: &cancelables)

// Add buttons to control the light presets and labels
let lightButton = changeLightButton()
let labelsButton = changeLabelsButton()
navigationItem.rightBarButtonItems = [lightButton, labelsButton]
let realEstateButton = changeRealEstateButton()
navigationItem.rightBarButtonItems = [lightButton, labelsButton, realEstateButton]
}

private func changeLightButton() -> UIBarButtonItem {
Expand All @@ -91,6 +86,14 @@ final class StandardStyleExample: UIViewController, ExampleProtocol {
}
}

private func changeRealEstateButton() -> UIBarButtonItem {
if #available(iOS 13, *) {
return UIBarButtonItem(image: UIImage(systemName: "building.2.fill"), style: .plain, target: self, action: #selector(changeRealEstateSettings))
} else {
return UIBarButtonItem(title: "Build", style: .plain, target: self, action: #selector(changeLabelsSetting))
}
}

@objc private func changeLightSetting() {
// When a user clicks the light setting button change the `lightPreset` config property on the Standard style import.

Expand All @@ -107,4 +110,24 @@ final class StandardStyleExample: UIViewController, ExampleProtocol {

mapView.mapboxMap.mapStyle = mapStyle
}

@objc private func changeRealEstateSettings() {
// When a user clicks show real estate button. Style import with real estate is added or deleted.
showRealEstate.toggle()
toggleRealEstate(isOn: showRealEstate)
}

private func toggleRealEstate(isOn: Bool) {
do {
if isOn {
try mapView.mapboxMap.addStyleImport(withId: "real-estate", uri: StyleURI(url: styleURL)!)
} else {
try mapView.mapboxMap.removeStyleImport(withId: "real-estate")
}
} catch {
print(error)
}
}
}

private let styleURL = Bundle.main.url(forResource: "fragment-realestate-NY", withExtension: "json")!
2 changes: 1 addition & 1 deletion Apps/Examples/Examples/Models/Examples.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ struct Examples {
Example(title: "Display a map view",
description: "Create and display a map that uses the default Mapbox Standard style.",
type: BasicMapExample.self),
Example(title: "Work with the Standard style",
Example(title: "Standard Style",
description: "Use the Standard style and modify settings at runtime.",
type: StandardStyleExample.self),
Example(title: "Debug Map",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,33 @@ import SwiftUI

@available(iOS 14.0, *)
struct StandardStyleImportExample: View {
@State private var lightPreset: StandardLightPreset = .night
@State private var lightPreset: StandardLightPreset? = .night
@State private var showLabels = true
@State private var priceAlertMessage: String?
@State private var panelHeight: CGFloat = 0

var style: MapStyle {
let styleURI = StyleURI(url: styleURL)!
return MapStyle(
uri: styleURI,
importConfigurations: [
// The 'fragment-realestate-NY.json' style imports standard style with "standard" import id.
// Here we specify import config to that style.
.standard(
importId: "standard",
lightPreset: lightPreset,
showPointOfInterestLabels: showLabels,
showTransitLabels: showLabels,
showPlaceLabels: showLabels,
showRoadLabels: showLabels)
]
)
}
@State private var showRealEstate = false

var body: some View {
Map(initialViewport: .camera(center: .init(latitude: 40.72, longitude: -73.99), zoom: 11, pitch: 45))
.mapStyle(style)
.onLayerTapGesture("NY-hotels-price") { queriedFeature, _ in
// Show house price by tap to the 'NY-hotels-price' layer defined in 'fragment-realestate-NY.json' style.
// The QueriedFeature represents the rendered GeoJSON feature that was tapped.
guard let price = queriedFeature.feature.properties?["price"],
case .number(let priceNum) = price else { return true }
priceAlertMessage = "Price: $\(String(format: "%.2f", priceNum))"
return true
}
.additionalSafeAreaInsets(.bottom, panelHeight)
.ignoresSafeArea()
.safeOverlay(alignment: .bottom) {
settingsPanel.onChangeOfSize { panelHeight = $0.height }
Map(initialViewport: .camera(center: .init(latitude: 40.72, longitude: -73.99), zoom: 11, pitch: 45)) {
StyleImport(style: .standard(
lightPreset: lightPreset,
showPointOfInterestLabels: showLabels,
showTransitLabels: showLabels,
showPlaceLabels: showLabels,
showRoadLabels: showLabels)
)

if showRealEstate {
StyleImport(uri: StyleURI(url: styleURL)!)
}
.simpleAlert(message: $priceAlertMessage)
}
.mapStyle(.empty)
.additionalSafeAreaInsets(.bottom, panelHeight)
.ignoresSafeArea()
.safeOverlay(alignment: .bottom) {
settingsPanel.onChangeOfSize { panelHeight = $0.height }
}
.simpleAlert(message: $priceAlertMessage)
}

@ViewBuilder
Expand All @@ -51,13 +38,15 @@ struct StandardStyleImportExample: View {
HStack {
Text("Light")
Picker("Light preset", selection: $lightPreset) {
Text("Dawn").tag(StandardLightPreset.dawn)
Text("Day").tag(StandardLightPreset.day)
Text("Dusk").tag(StandardLightPreset.dusk)
Text("Night").tag(StandardLightPreset.night)
Text("Dawn").tag(Optional(StandardLightPreset.dawn))
Text("Day").tag(Optional(StandardLightPreset.day))
Text("Dusk").tag(Optional(StandardLightPreset.dusk))
Text("Night").tag(Optional(StandardLightPreset.night))
Text("None").tag(Optional<StandardLightPreset>.none)
}.pickerStyle(.segmented)
}
Toggle("Labels", isOn: $showLabels)
Toggle("Show Real Estate", isOn: $showRealEstate)
}
.padding(10)
.floating(RoundedRectangle(cornerRadius: 10))
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,17 @@
Mapbox welcomes participation and contributions from everyone.

## main

### Experimental API breaking changes ⚠️
* `StyleImportConfiguration` now accepts only the configuration JSONObject without `importId`. And has `JSONObject` as `rawValue`
* `MapStyle` now accepts `importConfigurations` as a map where the key is `importId` instead of an array
* `StyleImportConfiguration.standard` is no more exposed, use `MapStyle.standard()` to provide configuration for Standard style

* Allow to assign slot to 2D and 3D location indicators.
* Allow to add slots at runtime.
* Expose API to interact with styile imports using Declarative Styling and regular imperative API.
* Expose `StyleImport` for declarative styling as `MapStyleContent`.
* Expose `removeStyleImport`, `moveStyleImport`, `updateStyleImport`, `addStyleImport` methods on `StyleManager`

## 11.3.0 - 10 April, 2024

Expand Down
2 changes: 1 addition & 1 deletion MapboxMaps.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Pod::Spec.new do |m|
m.source_files = 'Sources/MapboxMaps/**/*.{swift,h}'
m.resource_bundles = { 'MapboxMapsResources' => ['Sources/**/*.{xcassets,strings}', 'Sources/MapboxMaps/MapboxMaps.json', 'Sources/MapboxMaps/PrivacyInfo.xcprivacy'] }

m.dependency 'MapboxCoreMaps', '11.3.0'
m.dependency 'MapboxCoreMaps', '11.4.0-SNAPSHOT.0417T1228Z.b5204fc'
m.dependency 'MapboxCommon', '24.3.1'
m.dependency 'Turf', '2.8.0'

Expand Down
9 changes: 0 additions & 9 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,6 @@
"version" : "24.3.1"
}
},
{
"identity" : "mapbox-core-maps-ios",
"kind" : "remoteSourceControl",
"location" : "https://github.com/mapbox/mapbox-core-maps-ios.git",
"state" : {
"revision" : "3638d2841f8a2ef6a995c659457d6133a6f0d1be",
"version" : "11.3.0"
}
},
{
"identity" : "turf-swift",
"kind" : "remoteSourceControl",
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import PackageDescription
import Foundation

let coreMaps = MapsDependency.coreMaps(version: "11.3.0")
let coreMaps = MapsDependency.coreMaps(version: "11.4.0-SNAPSHOT.0417T1228Z.b5204fc", checksum: "5ad1d2c4f0e027bb23b149784dac08c14ac9d9f19f9e8a972e34fd809d188da9")
let common = MapsDependency.common(version: "24.3.1")

let mapboxMapsPath: String? = nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ final class MapStyleReconciler {
) {
let oldMapStyle = _mapStyle

if _mapStyle?.loadMethod != style.loadMethod {
if _mapStyle?.data != style.data {
_mapStyle = style

let callbacks = RuntimeStylingCallbacks(
Expand All @@ -45,7 +45,7 @@ final class MapStyleReconciler {
/// can continue loading.
/// We can safely add new content via style DSL.
guard let self else { return }
self.reconcileStyleImports(from: oldMapStyle?.importConfigurations)
self.reconcileBasemapConfiguration(from: oldMapStyle?.configuration)
self._isStyleRootLoaded.value = true
if let transition {
self.styleManager.setStyleTransitionFor(transition.coreOptions)
Expand All @@ -67,7 +67,7 @@ final class MapStyleReconciler {

self._isStyleRootLoaded.value = false

switch style.loadMethod {
switch style.data {
case let .json(json):
styleManager.setStyleJSON(json, callbacks: callbacks)
case let .uri(uri):
Expand All @@ -78,7 +78,7 @@ final class MapStyleReconciler {
_mapStyle = style

if _isStyleRootLoaded.value {
reconcileStyleImports(from: oldMapStyle?.importConfigurations)
reconcileBasemapConfiguration(from: oldMapStyle?.configuration)
}

if styleManager.isStyleLoaded() {
Expand All @@ -97,35 +97,34 @@ final class MapStyleReconciler {
}
}

private func reconcileStyleImports(from old: [StyleImportConfiguration]?) {
private func reconcileBasemapConfiguration(from old: JSONObject?) {
guard let mapStyle else { return }
Self.reconcileStyleImports(
Self.reconcileBasemapConfiguration(
from: old,
to: mapStyle.importConfigurations,
styleManager: styleManager)
to: mapStyle.configuration,
styleManager: styleManager
)
}
}

extension MapStyleReconciler {
static func reconcileStyleImports(
from old: [StyleImportConfiguration]?,
to new: [StyleImportConfiguration],
static func reconcileBasemapConfiguration(
from old: JSONObject?,
to new: JSONObject?,
styleManager: StyleManagerProtocol
) {
for importConfig in new {
let oldImportConfig = old?.first { $0.importId == importConfig.importId }
do {
for (key, value) in importConfig.config {
guard let value, value != oldImportConfig?.config[key] else {
continue
}
try handleExpected {
styleManager.setStyleImportConfigPropertyForImportId(importConfig.importId, config: key, value: value.rawValue)
}
guard let new else { return }
do {
for (key, value) in new {
guard let value, value != old?[key] else {
continue
}
try handleExpected {
styleManager.setStyleImportConfigPropertyForImportId("basemap", config: key, value: value.rawValue)
}
} catch {
Log.error(forMessage: "Failed updating import config properties, \(error)")
}
} catch {
Log.error(forMessage: "Failed updating import config properties, \(error)")
}
}
}
Loading

0 comments on commit 9a70af6

Please sign in to comment.