|
| 1 | +import UIKit |
| 2 | +@_spi(Experimental) import MapboxMaps |
| 3 | + |
| 4 | +final class StandardStyleInteractionsExample: UIViewController, ExampleProtocol { |
| 5 | + private var mapView: MapView! |
| 6 | + private var cancelables = Set<AnyCancelable>() |
| 7 | + var lightPreset = StandardLightPreset.day |
| 8 | + var theme = StandardTheme.default |
| 9 | + var buildingSelectColor = StyleColor("hsl(214, 94%, 59%)") // default color |
| 10 | + |
| 11 | + override func viewDidLoad() { |
| 12 | + super.viewDidLoad() |
| 13 | + |
| 14 | + let cameraCenter = CLLocationCoordinate2D(latitude: 60.1718, longitude: 24.9453) |
| 15 | + let options = MapInitOptions(cameraOptions: CameraOptions(center: cameraCenter, zoom: 16.35, bearing: 49.92, pitch: 40)) |
| 16 | + mapView = MapView(frame: view.bounds, mapInitOptions: options) |
| 17 | + mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight] |
| 18 | + |
| 19 | + /// DON'T USE Standard Experimental style in production, it will break over time. |
| 20 | + /// Currently this feature is in preview. |
| 21 | + mapView.mapboxMap.mapStyle = .standardExperimental() |
| 22 | + |
| 23 | + view.addSubview(mapView) |
| 24 | + mapView.mapboxMap.onStyleLoaded.observe { [weak self] _ in |
| 25 | + guard let self = self else { return } |
| 26 | + self.setupInteractions() |
| 27 | + finish() |
| 28 | + }.store(in: &cancelables) |
| 29 | + |
| 30 | + // Add UI elements for debug panel |
| 31 | + setupDebugPanel() |
| 32 | + } |
| 33 | + |
| 34 | + private func setupInteractions() { |
| 35 | + /// When a POI feature in the Standard POI featureset is tapped replace it with a ViewAnnotation |
| 36 | + mapView.mapboxMap.addInteraction(TapInteraction(.standardPoi) { [weak self] poi, _ in |
| 37 | + guard let self = self else { return false } |
| 38 | + self.addViewAnnotation(for: poi) |
| 39 | + self.mapView.mapboxMap.setFeatureState(poi, state: .init(hide: true)) |
| 40 | + return true /// Returning true stops propagation to features below or the map itself. |
| 41 | + }) |
| 42 | + |
| 43 | + /// When a building in the Standard Buildings featureset is tapped, set that building as selected to color it. |
| 44 | + mapView.mapboxMap.addInteraction(TapInteraction(.standardBuildings) { [weak self] building, _ in |
| 45 | + guard let self = self else { return false } |
| 46 | + self.mapView.mapboxMap.setFeatureState(building, state: .init(select: true)) |
| 47 | + return true |
| 48 | + }) |
| 49 | + |
| 50 | + /// When a place label in the Standard Place Labels featureset is tapped, set that place label as selected. |
| 51 | + mapView.mapboxMap.addInteraction(TapInteraction(.standardPlaceLabels) { [weak self] placeLabel, _ in |
| 52 | + guard let self = self else { return false } |
| 53 | + self.mapView.mapboxMap.setFeatureState(placeLabel, state: .init(select: true)) |
| 54 | + return true |
| 55 | + }) |
| 56 | + |
| 57 | + /// When the map is long-pressed, reset all selections |
| 58 | + mapView.mapboxMap.addInteraction(LongPressInteraction { [weak self] _ in |
| 59 | + guard let self = self else { return false } |
| 60 | + self.mapView.mapboxMap.resetFeatureStates(featureset: .standardBuildings, callback: nil) |
| 61 | + self.mapView.mapboxMap.resetFeatureStates(featureset: .standardPoi, callback: nil) |
| 62 | + self.mapView.mapboxMap.resetFeatureStates(featureset: .standardPlaceLabels, callback: nil) |
| 63 | + self.mapView.viewAnnotations.removeAll() |
| 64 | + return true |
| 65 | + }) |
| 66 | + } |
| 67 | + |
| 68 | + private func addViewAnnotation(for poi: StandardPoiFeature) { |
| 69 | + let view = UIImageView(image: UIImage(named: "intermediate-pin")) |
| 70 | + view.contentMode = .scaleAspectFit |
| 71 | + let annotation = ViewAnnotation(coordinate: poi.coordinate, view: view) |
| 72 | + annotation.variableAnchors = [.init(anchor: .bottom, offsetY: 12)] |
| 73 | + mapView.viewAnnotations.add(annotation) |
| 74 | + } |
| 75 | + |
| 76 | + private func setupDebugPanel() { |
| 77 | + let debugPanel = UIView() |
| 78 | + debugPanel.translatesAutoresizingMaskIntoConstraints = false |
| 79 | + debugPanel.backgroundColor = .white |
| 80 | + debugPanel.layer.cornerRadius = 10 |
| 81 | + debugPanel.layer.shadowColor = UIColor.black.cgColor |
| 82 | + debugPanel.layer.shadowOpacity = 0.2 |
| 83 | + debugPanel.layer.shadowOffset = CGSize(width: 0, height: 2) |
| 84 | + debugPanel.layer.shadowRadius = 4 |
| 85 | + view.addSubview(debugPanel) |
| 86 | + |
| 87 | + let buildingSelectLabel = UILabel() |
| 88 | + buildingSelectLabel.text = "Building Select" |
| 89 | + buildingSelectLabel.translatesAutoresizingMaskIntoConstraints = false |
| 90 | + debugPanel.addSubview(buildingSelectLabel) |
| 91 | + |
| 92 | + let buildingSelectControl = UISegmentedControl(items: ["Default", "Yellow", "Red"]) |
| 93 | + buildingSelectControl.selectedSegmentIndex = 0 |
| 94 | + buildingSelectControl.addTarget(self, action: #selector(buildingSelectColorChanged(_:)), for: .valueChanged) |
| 95 | + buildingSelectControl.translatesAutoresizingMaskIntoConstraints = false |
| 96 | + debugPanel.addSubview(buildingSelectControl) |
| 97 | + |
| 98 | + let lightLabel = UILabel() |
| 99 | + lightLabel.text = "Light" |
| 100 | + lightLabel.translatesAutoresizingMaskIntoConstraints = false |
| 101 | + debugPanel.addSubview(lightLabel) |
| 102 | + |
| 103 | + let lightControl = UISegmentedControl(items: ["Dawn", "Day", "Dusk", "Night"]) |
| 104 | + lightControl.selectedSegmentIndex = 1 |
| 105 | + lightControl.addTarget(self, action: #selector(lightPresetChanged(_:)), for: .valueChanged) |
| 106 | + lightControl.translatesAutoresizingMaskIntoConstraints = false |
| 107 | + debugPanel.addSubview(lightControl) |
| 108 | + |
| 109 | + let themeLabel = UILabel() |
| 110 | + themeLabel.text = "Theme" |
| 111 | + themeLabel.translatesAutoresizingMaskIntoConstraints = false |
| 112 | + debugPanel.addSubview(themeLabel) |
| 113 | + |
| 114 | + let themeControl = UISegmentedControl(items: ["Default", "Faded", "Monochrome"]) |
| 115 | + themeControl.selectedSegmentIndex = 0 |
| 116 | + themeControl.addTarget(self, action: #selector(themeChanged(_:)), for: .valueChanged) |
| 117 | + themeControl.translatesAutoresizingMaskIntoConstraints = false |
| 118 | + debugPanel.addSubview(themeControl) |
| 119 | + |
| 120 | + NSLayoutConstraint.activate([ |
| 121 | + debugPanel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10), |
| 122 | + debugPanel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10), |
| 123 | + debugPanel.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -10), |
| 124 | + |
| 125 | + buildingSelectLabel.topAnchor.constraint(equalTo: debugPanel.topAnchor, constant: 10), |
| 126 | + buildingSelectLabel.leadingAnchor.constraint(equalTo: debugPanel.leadingAnchor, constant: 10), |
| 127 | + |
| 128 | + buildingSelectControl.topAnchor.constraint(equalTo: buildingSelectLabel.bottomAnchor, constant: 5), |
| 129 | + buildingSelectControl.leadingAnchor.constraint(equalTo: debugPanel.leadingAnchor, constant: 10), |
| 130 | + buildingSelectControl.trailingAnchor.constraint(equalTo: debugPanel.trailingAnchor, constant: -10), |
| 131 | + |
| 132 | + lightLabel.topAnchor.constraint(equalTo: buildingSelectControl.bottomAnchor, constant: 10), |
| 133 | + lightLabel.leadingAnchor.constraint(equalTo: debugPanel.leadingAnchor, constant: 10), |
| 134 | + |
| 135 | + lightControl.topAnchor.constraint(equalTo: lightLabel.bottomAnchor, constant: 5), |
| 136 | + lightControl.leadingAnchor.constraint(equalTo: debugPanel.leadingAnchor, constant: 10), |
| 137 | + lightControl.trailingAnchor.constraint(equalTo: debugPanel.trailingAnchor, constant: -10), |
| 138 | + |
| 139 | + themeLabel.topAnchor.constraint(equalTo: lightControl.bottomAnchor, constant: 10), |
| 140 | + themeLabel.leadingAnchor.constraint(equalTo: debugPanel.leadingAnchor, constant: 10), |
| 141 | + |
| 142 | + themeControl.topAnchor.constraint(equalTo: themeLabel.bottomAnchor, constant: 5), |
| 143 | + themeControl.leadingAnchor.constraint(equalTo: debugPanel.leadingAnchor, constant: 10), |
| 144 | + themeControl.trailingAnchor.constraint(equalTo: debugPanel.trailingAnchor, constant: -10), |
| 145 | + |
| 146 | + themeControl.bottomAnchor.constraint(equalTo: debugPanel.bottomAnchor, constant: -10) |
| 147 | + ]) |
| 148 | + } |
| 149 | + |
| 150 | + @objc private func buildingSelectColorChanged(_ sender: UISegmentedControl) { |
| 151 | + switch sender.selectedSegmentIndex { |
| 152 | + case 0: |
| 153 | + buildingSelectColor = StyleColor("hsl(214, 94%, 59%)") |
| 154 | + case 1: |
| 155 | + buildingSelectColor = StyleColor("yellow") |
| 156 | + case 2: |
| 157 | + buildingSelectColor = StyleColor(.red) |
| 158 | + default: |
| 159 | + break |
| 160 | + } |
| 161 | + mapView.mapboxMap.mapStyle = .standardExperimental(theme: theme, lightPreset: lightPreset, buildingSelectColor: buildingSelectColor) |
| 162 | + } |
| 163 | + |
| 164 | + @objc private func lightPresetChanged(_ sender: UISegmentedControl) { |
| 165 | + switch sender.selectedSegmentIndex { |
| 166 | + case 0: |
| 167 | + lightPreset = .dawn |
| 168 | + case 1: |
| 169 | + lightPreset = .day |
| 170 | + case 2: |
| 171 | + lightPreset = .dusk |
| 172 | + case 3: |
| 173 | + lightPreset = .night |
| 174 | + default: |
| 175 | + break |
| 176 | + } |
| 177 | + mapView.mapboxMap.mapStyle = .standardExperimental(theme: theme, lightPreset: lightPreset, buildingSelectColor: buildingSelectColor) |
| 178 | + } |
| 179 | + |
| 180 | + @objc private func themeChanged(_ sender: UISegmentedControl) { |
| 181 | + switch sender.selectedSegmentIndex { |
| 182 | + case 0: |
| 183 | + theme = .default |
| 184 | + case 1: |
| 185 | + theme = .faded |
| 186 | + case 2: |
| 187 | + theme = .monochrome |
| 188 | + default: |
| 189 | + break |
| 190 | + } |
| 191 | + mapView.mapboxMap.mapStyle = .standardExperimental(theme: theme, lightPreset: lightPreset, buildingSelectColor: buildingSelectColor) |
| 192 | + } |
| 193 | +} |
0 commit comments