Skip to content

Commit

Permalink
Update examples and add new interactions example (#2450)
Browse files Browse the repository at this point in the history
Co-authored-by: Release SDK bot for Maps SDK team <[email protected]>
  • Loading branch information
pjleonard37 and Release SDK bot for Maps SDK team authored Feb 14, 2025
1 parent 1f5a303 commit f9f2210
Show file tree
Hide file tree
Showing 6 changed files with 459 additions and 162 deletions.
228 changes: 126 additions & 102 deletions Examples.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

89 changes: 85 additions & 4 deletions Sources/Examples/All Examples/StandardStyleExample.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import UIKit
import MapboxMaps
@_spi(Experimental) import MapboxMaps

final class StandardStyleExample: UIViewController, ExampleProtocol {
private var mapView: MapView!
private var cancelables = Set<AnyCancelable>()
private var lightPreset = StandardLightPreset.night
private var labelsSetting = true
private var showRealEstate = true
private var selectedPriceLabel: FeaturesetFeature?

private var mapStyle: MapStyle {
.standard(
Expand All @@ -30,7 +31,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 }
guard let self = self else { return }

// Create and apply basic styling to the line layer, assign the layer to the "bottom" slot
var layer = LineLayer(id: "line-layer", source: "line-layer")
Expand Down Expand Up @@ -63,6 +64,33 @@ final class StandardStyleExample: UIViewController, ExampleProtocol {
finish()
}.store(in: &cancelables)

/// The contents of the imported style are private, meaning all the implementation details such as layers and sources are not accessible at runtime.
/// However the style defines a "hotels-price" featureset that represents a portion of features available for interaction.
/// Using the Interactions API you can add interactions to featuresets.
/// See `fragment-realestate-NY.json` for more information.
mapView.mapboxMap.addInteraction(TapInteraction(.featureset("hotels-price", importId: "real-estate-fragment")) { [weak self] priceLabel, _ in
guard let self = self else { return false }
/// Select a price label when it's clicked
self.selectedPriceLabel = priceLabel

/// When there's a selected price label, we use it to set a feature state.
/// The `hidden` state is implemented in `fragment-realestate-NY.json` and hides the label and icon.
self.mapView.mapboxMap.setFeatureState(priceLabel, state: ["hidden": true])

self.updateViewAnnotation()
return true
})

/// An interaction without specified featureset handles all corresponding events that haven't been handled by other interactions.
mapView.mapboxMap.addInteraction(TapInteraction { [weak self] _ in
guard let self = self else { return false }
/// When the user taps the map outside of the price labels, deselect the latest selected label.
self.selectedPriceLabel = nil
self.mapView.mapboxMap.resetFeatureStates(featureset: .featureset("hotels-price", importId: "real-estate-fragment"), callback: nil)
self.updateViewAnnotation()
return true
})

// Add buttons to control the light presets and labels
let lightButton = changeLightButton()
let labelsButton = changeLabelsButton()
Expand Down Expand Up @@ -108,14 +136,67 @@ final class StandardStyleExample: UIViewController, ExampleProtocol {
private func toggleRealEstate(isOn: Bool) {
do {
if isOn {
try mapView.mapboxMap.addStyleImport(withId: "real-estate", uri: StyleURI(url: styleURL)!)
try mapView.mapboxMap.addStyleImport(withId: "real-estate-fragment", uri: StyleURI(url: styleURL)!)
} else {
try mapView.mapboxMap.removeStyleImport(withId: "real-estate")
try mapView.mapboxMap.removeStyleImport(withId: "real-estate-fragment")
}
} catch {
print(error)
}
}

private func updateViewAnnotation() {
mapView.viewAnnotations.removeAll()

if let selectedPriceLabel = selectedPriceLabel, let coordinate = selectedPriceLabel.geometry.point?.coordinates {
let calloutView = createCalloutView(for: selectedPriceLabel)
let annotation = ViewAnnotation(coordinate: coordinate, view: calloutView)
annotation.variableAnchors = [.init(anchor: .bottom)]
mapView.viewAnnotations.add(annotation)
}
}

private func createCalloutView(for feature: FeaturesetFeature) -> UIView {
let calloutView = UIView()
calloutView.backgroundColor = .white
calloutView.layer.cornerRadius = 8
calloutView.layer.shadowColor = UIColor.black.cgColor
calloutView.layer.shadowOpacity = 0.2
calloutView.layer.shadowOffset = CGSize(width: 0, height: 2)
calloutView.layer.shadowRadius = 4

let nameLabel = UILabel()
nameLabel.text = feature.name ?? ""
nameLabel.font = UIFont.boldSystemFont(ofSize: 16)
nameLabel.translatesAutoresizingMaskIntoConstraints = false

let priceLabel = UILabel()
priceLabel.text = feature.price ?? ""
priceLabel.font = UIFont.systemFont(ofSize: 14)
priceLabel.textColor = .gray
priceLabel.translatesAutoresizingMaskIntoConstraints = false

calloutView.addSubview(nameLabel)
calloutView.addSubview(priceLabel)

NSLayoutConstraint.activate([
nameLabel.topAnchor.constraint(equalTo: calloutView.topAnchor, constant: 8),
nameLabel.leadingAnchor.constraint(equalTo: calloutView.leadingAnchor, constant: 8),
nameLabel.trailingAnchor.constraint(equalTo: calloutView.trailingAnchor, constant: -8),

priceLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 4),
priceLabel.leadingAnchor.constraint(equalTo: calloutView.leadingAnchor, constant: 8),
priceLabel.trailingAnchor.constraint(equalTo: calloutView.trailingAnchor, constant: -8),
priceLabel.bottomAnchor.constraint(equalTo: calloutView.bottomAnchor, constant: -8)
])

return calloutView
}
}

private extension FeaturesetFeature {
var price: String? { properties["price"]??.number.map { "$ \($0)" } }
var name: String? { properties["name"]??.string }
}

private let styleURL = Bundle.main.url(forResource: "fragment-realestate-NY", withExtension: "json")!
193 changes: 193 additions & 0 deletions Sources/Examples/All Examples/StandardStyleInteractionsExample.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import UIKit
@_spi(Experimental) import MapboxMaps

final class StandardStyleInteractionsExample: UIViewController, ExampleProtocol {
private var mapView: MapView!
private var cancelables = Set<AnyCancelable>()
var lightPreset = StandardLightPreset.day
var theme = StandardTheme.default
var buildingSelectColor = StyleColor("hsl(214, 94%, 59%)") // default color

override func viewDidLoad() {
super.viewDidLoad()

let cameraCenter = CLLocationCoordinate2D(latitude: 60.1718, longitude: 24.9453)
let options = MapInitOptions(cameraOptions: CameraOptions(center: cameraCenter, zoom: 16.35, bearing: 49.92, pitch: 40))
mapView = MapView(frame: view.bounds, mapInitOptions: options)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]

/// DON'T USE Standard Experimental style in production, it will break over time.
/// Currently this feature is in preview.
mapView.mapboxMap.mapStyle = .standardExperimental()

view.addSubview(mapView)
mapView.mapboxMap.onStyleLoaded.observe { [weak self] _ in
guard let self = self else { return }
self.setupInteractions()
finish()
}.store(in: &cancelables)

// Add UI elements for debug panel
setupDebugPanel()
}

private func setupInteractions() {
/// When a POI feature in the Standard POI featureset is tapped replace it with a ViewAnnotation
mapView.mapboxMap.addInteraction(TapInteraction(.standardPoi) { [weak self] poi, _ in
guard let self = self else { return false }
self.addViewAnnotation(for: poi)
self.mapView.mapboxMap.setFeatureState(poi, state: .init(hide: true))
return true /// Returning true stops propagation to features below or the map itself.
})

/// When a building in the Standard Buildings featureset is tapped, set that building as selected to color it.
mapView.mapboxMap.addInteraction(TapInteraction(.standardBuildings) { [weak self] building, _ in
guard let self = self else { return false }
self.mapView.mapboxMap.setFeatureState(building, state: .init(select: true))
return true
})

/// When a place label in the Standard Place Labels featureset is tapped, set that place label as selected.
mapView.mapboxMap.addInteraction(TapInteraction(.standardPlaceLabels) { [weak self] placeLabel, _ in
guard let self = self else { return false }
self.mapView.mapboxMap.setFeatureState(placeLabel, state: .init(select: true))
return true
})

/// When the map is long-pressed, reset all selections
mapView.mapboxMap.addInteraction(LongPressInteraction { [weak self] _ in
guard let self = self else { return false }
self.mapView.mapboxMap.resetFeatureStates(featureset: .standardBuildings, callback: nil)
self.mapView.mapboxMap.resetFeatureStates(featureset: .standardPoi, callback: nil)
self.mapView.mapboxMap.resetFeatureStates(featureset: .standardPlaceLabels, callback: nil)
self.mapView.viewAnnotations.removeAll()
return true
})
}

private func addViewAnnotation(for poi: StandardPoiFeature) {
let view = UIImageView(image: UIImage(named: "intermediate-pin"))
view.contentMode = .scaleAspectFit
let annotation = ViewAnnotation(coordinate: poi.coordinate, view: view)
annotation.variableAnchors = [.init(anchor: .bottom, offsetY: 12)]
mapView.viewAnnotations.add(annotation)
}

private func setupDebugPanel() {
let debugPanel = UIView()
debugPanel.translatesAutoresizingMaskIntoConstraints = false
debugPanel.backgroundColor = .white
debugPanel.layer.cornerRadius = 10
debugPanel.layer.shadowColor = UIColor.black.cgColor
debugPanel.layer.shadowOpacity = 0.2
debugPanel.layer.shadowOffset = CGSize(width: 0, height: 2)
debugPanel.layer.shadowRadius = 4
view.addSubview(debugPanel)

let buildingSelectLabel = UILabel()
buildingSelectLabel.text = "Building Select"
buildingSelectLabel.translatesAutoresizingMaskIntoConstraints = false
debugPanel.addSubview(buildingSelectLabel)

let buildingSelectControl = UISegmentedControl(items: ["Default", "Yellow", "Red"])
buildingSelectControl.selectedSegmentIndex = 0
buildingSelectControl.addTarget(self, action: #selector(buildingSelectColorChanged(_:)), for: .valueChanged)
buildingSelectControl.translatesAutoresizingMaskIntoConstraints = false
debugPanel.addSubview(buildingSelectControl)

let lightLabel = UILabel()
lightLabel.text = "Light"
lightLabel.translatesAutoresizingMaskIntoConstraints = false
debugPanel.addSubview(lightLabel)

let lightControl = UISegmentedControl(items: ["Dawn", "Day", "Dusk", "Night"])
lightControl.selectedSegmentIndex = 1
lightControl.addTarget(self, action: #selector(lightPresetChanged(_:)), for: .valueChanged)
lightControl.translatesAutoresizingMaskIntoConstraints = false
debugPanel.addSubview(lightControl)

let themeLabel = UILabel()
themeLabel.text = "Theme"
themeLabel.translatesAutoresizingMaskIntoConstraints = false
debugPanel.addSubview(themeLabel)

let themeControl = UISegmentedControl(items: ["Default", "Faded", "Monochrome"])
themeControl.selectedSegmentIndex = 0
themeControl.addTarget(self, action: #selector(themeChanged(_:)), for: .valueChanged)
themeControl.translatesAutoresizingMaskIntoConstraints = false
debugPanel.addSubview(themeControl)

NSLayoutConstraint.activate([
debugPanel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
debugPanel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
debugPanel.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -10),

buildingSelectLabel.topAnchor.constraint(equalTo: debugPanel.topAnchor, constant: 10),
buildingSelectLabel.leadingAnchor.constraint(equalTo: debugPanel.leadingAnchor, constant: 10),

buildingSelectControl.topAnchor.constraint(equalTo: buildingSelectLabel.bottomAnchor, constant: 5),
buildingSelectControl.leadingAnchor.constraint(equalTo: debugPanel.leadingAnchor, constant: 10),
buildingSelectControl.trailingAnchor.constraint(equalTo: debugPanel.trailingAnchor, constant: -10),

lightLabel.topAnchor.constraint(equalTo: buildingSelectControl.bottomAnchor, constant: 10),
lightLabel.leadingAnchor.constraint(equalTo: debugPanel.leadingAnchor, constant: 10),

lightControl.topAnchor.constraint(equalTo: lightLabel.bottomAnchor, constant: 5),
lightControl.leadingAnchor.constraint(equalTo: debugPanel.leadingAnchor, constant: 10),
lightControl.trailingAnchor.constraint(equalTo: debugPanel.trailingAnchor, constant: -10),

themeLabel.topAnchor.constraint(equalTo: lightControl.bottomAnchor, constant: 10),
themeLabel.leadingAnchor.constraint(equalTo: debugPanel.leadingAnchor, constant: 10),

themeControl.topAnchor.constraint(equalTo: themeLabel.bottomAnchor, constant: 5),
themeControl.leadingAnchor.constraint(equalTo: debugPanel.leadingAnchor, constant: 10),
themeControl.trailingAnchor.constraint(equalTo: debugPanel.trailingAnchor, constant: -10),

themeControl.bottomAnchor.constraint(equalTo: debugPanel.bottomAnchor, constant: -10)
])
}

@objc private func buildingSelectColorChanged(_ sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case 0:
buildingSelectColor = StyleColor("hsl(214, 94%, 59%)")
case 1:
buildingSelectColor = StyleColor("yellow")
case 2:
buildingSelectColor = StyleColor(.red)
default:
break
}
mapView.mapboxMap.mapStyle = .standardExperimental(theme: theme, lightPreset: lightPreset, buildingSelectColor: buildingSelectColor)
}

@objc private func lightPresetChanged(_ sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case 0:
lightPreset = .dawn
case 1:
lightPreset = .day
case 2:
lightPreset = .dusk
case 3:
lightPreset = .night
default:
break
}
mapView.mapboxMap.mapStyle = .standardExperimental(theme: theme, lightPreset: lightPreset, buildingSelectColor: buildingSelectColor)
}

@objc private func themeChanged(_ sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case 0:
theme = .default
case 1:
theme = .faded
case 2:
theme = .monochrome
default:
break
}
mapView.mapboxMap.mapStyle = .standardExperimental(theme: theme, lightPreset: lightPreset, buildingSelectColor: buildingSelectColor)
}
}
3 changes: 3 additions & 0 deletions Sources/Examples/Models/Examples.swift
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,9 @@ struct Examples {

// Examples that show use cases related to user interaction with the map.
static let userInteractionExamples: [Example] = .init {
Example(title: "Standard Style Interactions",
description: "Showcase of Standard style interactions.",
type: StandardStyleInteractionsExample.self)
Example(title: "Find features at a point",
description: "Query the map for rendered features belonging to a specific layer.",
type: FeaturesAtPointExample.self)
Expand Down
Loading

0 comments on commit f9f2210

Please sign in to comment.