Skip to content

Commit d330665

Browse files
authored
Merge pull request #28 from stadiamaps/content-insets-and-more-modifiers
Content insets and more modifiers
2 parents 497ab5f + 879bd06 commit d330665

38 files changed

+612
-187
lines changed

.github/workflows/test.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
run: brew install swiftformat
1616

1717
- name: Checkout maplibre-swiftui-dsl-playground
18-
uses: actions/checkout@v3
18+
uses: actions/checkout@v4
1919

2020
- name: Check format
2121
run: swiftformat . --lint
@@ -38,10 +38,10 @@ jobs:
3838

3939
- uses: maxim-lobanov/setup-xcode@v1
4040
with:
41-
xcode-version: '15.0'
41+
xcode-version: '15.2'
4242

4343
- name: Checkout maplibre-swiftui-dsl-playground
44-
uses: actions/checkout@v3
44+
uses: actions/checkout@v4
4545

4646
- name: Test ${{ matrix.scheme }} on ${{ matrix.destination }}
4747
run: xcodebuild -scheme ${{ matrix.scheme }} test -skipMacroValidation -destination '${{ matrix.destination }}' | xcbeautify && exit ${PIPESTATUS[0]}

Package.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ let package = Package(
3838
],
3939
swiftSettings: [
4040
.define("MOCKING", .when(configuration: .debug)),
41+
.enableExperimentalFeature("StrictConcurrency"),
4142
]
4243
),
4344
.target(
@@ -46,10 +47,16 @@ let package = Package(
4647
.target(name: "InternalUtils"),
4748
.product(name: "MapLibre", package: "maplibre-gl-native-distribution"),
4849
.product(name: "MapLibreSwiftMacros", package: "maplibre-swift-macros"),
50+
],
51+
swiftSettings: [
52+
.enableExperimentalFeature("StrictConcurrency"),
4953
]
5054
),
5155
.target(
52-
name: "InternalUtils"
56+
name: "InternalUtils",
57+
swiftSettings: [
58+
.enableExperimentalFeature("StrictConcurrency"),
59+
]
5360
),
5461

5562
// MARK: Tests
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import Foundation
2+
import MapLibre
3+
4+
public protocol MapControl {
5+
/// Overrides the position of the control. Default values are control-specfiic.
6+
var position: MLNOrnamentPosition? { get set }
7+
/// Overrides the offset of the control.
8+
var margins: CGPoint? { get set }
9+
/// Overrides whether the control is hidden.
10+
var isHidden: Bool { get set }
11+
12+
@MainActor func configureMapView(_ mapView: MLNMapView)
13+
}
14+
15+
public extension MapControl {
16+
/// Sets position of the control.
17+
func position(_ position: MLNOrnamentPosition) -> Self {
18+
var result = self
19+
20+
result.position = position
21+
22+
return result
23+
}
24+
25+
/// Sets the position offset of the control.
26+
func margins(_ margins: CGPoint) -> Self {
27+
var result = self
28+
29+
result.margins = margins
30+
31+
return result
32+
}
33+
34+
/// Hides the control.
35+
func hidden(_: Bool) -> Self {
36+
var result = self
37+
38+
result.isHidden = true
39+
40+
return result
41+
}
42+
}
43+
44+
public struct CompassView: MapControl {
45+
public var position: MLNOrnamentPosition?
46+
public var margins: CGPoint?
47+
public var isHidden: Bool = false
48+
49+
public func configureMapView(_ mapView: MLNMapView) {
50+
if let position {
51+
mapView.compassViewPosition = position
52+
}
53+
54+
if let margins {
55+
mapView.compassViewMargins = margins
56+
}
57+
58+
mapView.compassView.isHidden = isHidden
59+
}
60+
61+
public init() {}
62+
}
63+
64+
public struct LogoView: MapControl {
65+
public var position: MLNOrnamentPosition?
66+
public var margins: CGPoint?
67+
public var isHidden: Bool = false
68+
public var image: UIImage?
69+
70+
public init() {}
71+
72+
public func configureMapView(_ mapView: MLNMapView) {
73+
if let position {
74+
mapView.logoViewPosition = position
75+
}
76+
77+
if let margins {
78+
mapView.logoViewMargins = margins
79+
}
80+
81+
mapView.logoView.isHidden = isHidden
82+
83+
if let image {
84+
mapView.logoView.image = image
85+
}
86+
}
87+
}
88+
89+
public extension LogoView {
90+
/// Sets the logo image (defaults to the MapLibre logo).
91+
func image(_ image: UIImage?) -> Self {
92+
var result = self
93+
94+
result.image = image
95+
96+
return result
97+
}
98+
}
99+
100+
@resultBuilder
101+
public enum MapControlsBuilder: DefaultResultBuilder {
102+
public static func buildExpression(_ expression: MapControl) -> [MapControl] {
103+
[expression]
104+
}
105+
106+
public static func buildExpression(_ expression: [MapControl]) -> [MapControl] {
107+
expression
108+
}
109+
110+
public static func buildExpression(_: Void) -> [MapControl] {
111+
[]
112+
}
113+
114+
public static func buildBlock(_ components: [MapControl]...) -> [MapControl] {
115+
components.flatMap { $0 }
116+
}
117+
118+
public static func buildArray(_ components: [MapControl]) -> [MapControl] {
119+
components
120+
}
121+
122+
public static func buildArray(_ components: [[MapControl]]) -> [MapControl] {
123+
components.flatMap { $0 }
124+
}
125+
126+
public static func buildEither(first components: [MapControl]) -> [MapControl] {
127+
components
128+
}
129+
130+
public static func buildEither(second components: [MapControl]) -> [MapControl] {
131+
components
132+
}
133+
134+
public static func buildOptional(_ components: [MapControl]?) -> [MapControl] {
135+
components ?? []
136+
}
137+
}
Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
11
import CoreLocation
22
import SwiftUI
33

4-
private let switzerland = CLLocationCoordinate2D(latitude: 46.801111, longitude: 8.226667)
5-
64
struct CameraDirectManipulationPreview: View {
75
@State private var camera = MapViewCamera.center(switzerland, zoom: 4)
86

97
let styleURL: URL
108
var onStyleLoaded: (() -> Void)? = nil
9+
var targetCameraAfterDelay: MapViewCamera? = nil
1110

1211
var body: some View {
1312
MapView(styleURL: styleURL, camera: $camera)
1413
.onStyleLoaded { _ in
15-
print("Style is loaded")
1614
onStyleLoaded?()
1715
}
1816
.overlay(alignment: .bottom, content: {
19-
Text("\(String(describing: camera.state)) z \(camera.zoom)")
17+
Text("\(String(describing: camera.state))")
2018
.padding()
2119
.foregroundColor(.white)
2220
.background(
@@ -27,16 +25,19 @@ struct CameraDirectManipulationPreview: View {
2725
.padding(.bottom, 42)
2826
})
2927
.task {
30-
try? await Task.sleep(nanoseconds: 3 * NSEC_PER_SEC)
28+
if let targetCameraAfterDelay {
29+
try? await Task.sleep(nanoseconds: 3 * NSEC_PER_SEC)
3130

32-
camera = MapViewCamera.center(switzerland, zoom: 6)
31+
camera = targetCameraAfterDelay
32+
}
3333
}
3434
}
3535
}
3636

37-
#Preview("Camera Preview") {
37+
#Preview("Camera Zoom after delay") {
3838
CameraDirectManipulationPreview(
39-
styleURL: URL(string: "https://demotiles.maplibre.org/style.json")!
39+
styleURL: demoTilesURL,
40+
targetCameraAfterDelay: .center(switzerland, zoom: 6)
4041
)
4142
.ignoresSafeArea(.all)
4243
}

Sources/MapLibreSwiftUI/Examples/Layers.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ import MapLibre
33
import MapLibreSwiftDSL
44
import SwiftUI
55

6-
let demoTilesURL = URL(string: "https://demotiles.maplibre.org/style.json")!
7-
86
// A collection of points with various
97
// attributes
8+
@MainActor
109
let pointSource = ShapeSource(identifier: "points") {
1110
// Uses the DSL to quickly construct point features inline
1211
MLNPointFeature(coordinate: CLLocationCoordinate2D(latitude: 51.47778, longitude: -0.00139))

Sources/MapLibreSwiftUI/Examples/Polyline.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ struct PolylinePreview: View {
88

99
var body: some View {
1010
MapView(styleURL: styleURL,
11-
constantCamera: .center(samplePedestrianWaypoints.first!, zoom: 14))
11+
camera: .constant(.center(samplePedestrianWaypoints.first!, zoom: 14)))
1212
{
1313
// Note: This line does not add the source to the style as if it
1414
// were a statement in an imperative programming language.
@@ -43,8 +43,6 @@ struct PolylinePreview: View {
4343

4444
struct Polyline_Previews: PreviewProvider {
4545
static var previews: some View {
46-
let demoTilesURL = URL(string: "https://demotiles.maplibre.org/style.json")!
47-
4846
PolylinePreview(styleURL: demoTilesURL)
4947
.ignoresSafeArea(.all)
5048
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// This file contains helpers that are used in the SwiftUI preview examples
2+
import CoreLocation
3+
4+
let switzerland = CLLocationCoordinate2D(latitude: 47.03041, longitude: 8.29470)
5+
let demoTilesURL =
6+
URL(string: "https://demotiles.maplibre.org/style.json")!
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import CoreLocation
2+
import MapLibreSwiftDSL
3+
import SwiftUI
4+
5+
@MainActor
6+
private let locationManager = StaticLocationManager(initialLocation: CLLocation(
7+
coordinate: switzerland,
8+
altitude: 0,
9+
horizontalAccuracy: 1,
10+
verticalAccuracy: 1,
11+
course: 8,
12+
speed: 28,
13+
timestamp: Date()
14+
))
15+
16+
#Preview("Track user location") {
17+
MapView(
18+
styleURL: demoTilesURL,
19+
camera: .constant(.trackUserLocation(zoom: 4, pitch: .fixed(45))),
20+
locationManager: locationManager
21+
)
22+
.mapViewContentInset(.init(top: 450, left: 0, bottom: 0, right: 0))
23+
.ignoresSafeArea(.all)
24+
}
25+
26+
#Preview("Track user location with Course") {
27+
MapView(
28+
styleURL: demoTilesURL,
29+
camera: .constant(.trackUserLocationWithCourse(zoom: 4, pitch: .fixed(45))),
30+
locationManager: locationManager
31+
)
32+
.mapViewContentInset(.init(top: 450, left: 0, bottom: 0, right: 0))
33+
.mapControls {
34+
LogoView()
35+
}
36+
.ignoresSafeArea(.all)
37+
}

Sources/MapLibreSwiftUI/Extensions/MapLibre/MLNMapViewCameraUpdating.swift

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,18 @@ import Foundation
33
import MapLibre
44
import Mockable
55

6+
// NOTE: We should eventually mark the entire protocol @MainActor, but Mockable generates some unsafe code at the moment
67
@Mockable
78
protocol MLNMapViewCameraUpdating: AnyObject {
8-
var userTrackingMode: MLNUserTrackingMode { get set }
9-
var minimumPitch: CGFloat { get set }
10-
var maximumPitch: CGFloat { get set }
11-
func setCenter(_ coordinate: CLLocationCoordinate2D,
12-
zoomLevel: Double,
13-
direction: CLLocationDirection,
14-
animated: Bool)
15-
func setZoomLevel(_ zoomLevel: Double, animated: Bool)
16-
func setVisibleCoordinateBounds(
9+
@MainActor var userTrackingMode: MLNUserTrackingMode { get set }
10+
@MainActor var minimumPitch: CGFloat { get set }
11+
@MainActor var maximumPitch: CGFloat { get set }
12+
@MainActor func setCenter(_ coordinate: CLLocationCoordinate2D,
13+
zoomLevel: Double,
14+
direction: CLLocationDirection,
15+
animated: Bool)
16+
@MainActor func setZoomLevel(_ zoomLevel: Double, animated: Bool)
17+
@MainActor func setVisibleCoordinateBounds(
1718
_ bounds: MLNCoordinateBounds,
1819
edgePadding: UIEdgeInsets,
1920
animated: Bool,

Sources/MapLibreSwiftUI/Extensions/MapView/MapViewGestures.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ extension MapView {
99
/// - mapView: The MLNMapView that will host the gesture itself.
1010
/// - context: The UIViewRepresentable context that will orchestrate the response sender
1111
/// - gesture: The gesture definition.
12-
func registerGesture(_ mapView: MLNMapView, _ context: Context, gesture: MapGesture) {
12+
@MainActor func registerGesture(_ mapView: MLNMapView, _ context: Context, gesture: MapGesture) {
1313
switch gesture.method {
1414
case let .tap(numberOfTaps: numberOfTaps):
1515
let gestureRecognizer = UITapGestureRecognizer(target: context.coordinator,
@@ -41,7 +41,7 @@ extension MapView {
4141
/// - mapView: The MapView emitting the gesture. This is used to calculate the point and coordinate of the
4242
/// gesture.
4343
/// - sender: The UIGestureRecognizer
44-
func processGesture(_ mapView: MLNMapView, _ sender: UIGestureRecognizer) {
44+
@MainActor func processGesture(_ mapView: MLNMapView, _ sender: UIGestureRecognizer) {
4545
guard let gesture = gestures.first(where: { $0.gestureRecognizer == sender }) else {
4646
assertionFailure("\(sender) is not a registered UIGestureRecongizer on the MapView")
4747
return
@@ -60,11 +60,11 @@ extension MapView {
6060
/// - gesture: The gesture definition for this event.
6161
/// - sender: The UIKit gesture emitting from the map view.
6262
/// - Returns: The calculated context from the sending UIKit gesture
63-
func processContextFromGesture(_ mapView: MLNMapView, gesture: MapGesture,
64-
sender: UIGestureRecognizing) -> MapGestureContext
63+
@MainActor func processContextFromGesture(_ mapView: MLNMapView, gesture: MapGesture,
64+
sender: UIGestureRecognizing) -> MapGestureContext
6565
{
6666
// Build the context of the gesture's event.
67-
var point: CGPoint = switch gesture.method {
67+
let point: CGPoint = switch gesture.method {
6868
case let .tap(numberOfTaps: numberOfTaps):
6969
// Calculate the CGPoint of the last gesture tap
7070
sender.location(ofTouch: numberOfTaps - 1, in: mapView)

0 commit comments

Comments
 (0)