diff --git a/Sources/MapLibreSwiftUI/Examples/User Location.swift b/Sources/MapLibreSwiftUI/Examples/User Location.swift index 3bafb2f..b30b575 100644 --- a/Sources/MapLibreSwiftUI/Examples/User Location.swift +++ b/Sources/MapLibreSwiftUI/Examples/User Location.swift @@ -16,7 +16,7 @@ private let locationManager = StaticLocationManager(initialLocation: CLLocation( #Preview("Track user location") { MapView( styleURL: demoTilesURL, - camera: .constant(.trackUserLocation(zoom: 4, pitch: .fixed(45))), + camera: .constant(.trackUserLocation(zoom: 4, pitch: 45)), locationManager: locationManager ) .mapViewContentInset(.init(top: 450, left: 0, bottom: 0, right: 0)) @@ -26,7 +26,7 @@ private let locationManager = StaticLocationManager(initialLocation: CLLocation( #Preview("Track user location with Course") { MapView( styleURL: demoTilesURL, - camera: .constant(.trackUserLocationWithCourse(zoom: 4, pitch: .fixed(45))), + camera: .constant(.trackUserLocationWithCourse(zoom: 4, pitch: 45)), locationManager: locationManager ) .mapViewContentInset(.init(top: 450, left: 0, bottom: 0, right: 0)) diff --git a/Sources/MapLibreSwiftUI/Extensions/MapLibre/MLNMapViewCameraUpdating.swift b/Sources/MapLibreSwiftUI/Extensions/MapLibre/MLNMapViewCameraUpdating.swift index d7f01a4..60bc362 100644 --- a/Sources/MapLibreSwiftUI/Extensions/MapLibre/MLNMapViewCameraUpdating.swift +++ b/Sources/MapLibreSwiftUI/Extensions/MapLibre/MLNMapViewCameraUpdating.swift @@ -10,6 +10,9 @@ protocol MLNMapViewCameraUpdating: AnyObject { @MainActor var minimumPitch: CGFloat { get set } @MainActor var maximumPitch: CGFloat { get set } @MainActor var direction: CLLocationDirection { get set } + @MainActor var camera: MLNMapCamera { get set } + @MainActor var frame: CGRect { get set } + @MainActor func setCamera(_ camera: MLNMapCamera, animated: Bool) @MainActor func setCenter(_ coordinate: CLLocationCoordinate2D, zoomLevel: Double, direction: CLLocationDirection, diff --git a/Sources/MapLibreSwiftUI/Extensions/MapViewCamera/MapViewCameraOperations.swift b/Sources/MapLibreSwiftUI/Extensions/MapViewCamera/MapViewCameraOperations.swift index a58f330..ef6db95 100644 --- a/Sources/MapLibreSwiftUI/Extensions/MapViewCamera/MapViewCameraOperations.swift +++ b/Sources/MapLibreSwiftUI/Extensions/MapViewCamera/MapViewCameraOperations.swift @@ -8,17 +8,18 @@ public extension MapViewCamera { /// - Parameter newZoom: The new zoom value. mutating func setZoom(_ newZoom: Double) { switch state { - case let .centered(onCoordinate, _, pitch, direction): + case let .centered(onCoordinate, _, pitch, pitchRange, direction): state = .centered(onCoordinate: onCoordinate, zoom: newZoom, pitch: pitch, + pitchRange: pitchRange, direction: direction) - case let .trackingUserLocation(_, pitch, direction): - state = .trackingUserLocation(zoom: newZoom, pitch: pitch, direction: direction) - case let .trackingUserLocationWithHeading(_, pitch): - state = .trackingUserLocationWithHeading(zoom: newZoom, pitch: pitch) - case let .trackingUserLocationWithCourse(_, pitch): - state = .trackingUserLocationWithCourse(zoom: newZoom, pitch: pitch) + case let .trackingUserLocation(_, pitch, pitchRange, direction): + state = .trackingUserLocation(zoom: newZoom, pitch: pitch, pitchRange: pitchRange, direction: direction) + case let .trackingUserLocationWithHeading(_, pitch, pitchRange): + state = .trackingUserLocationWithHeading(zoom: newZoom, pitch: pitch, pitchRange: pitchRange) + case let .trackingUserLocationWithCourse(_, pitch, pitchRange): + state = .trackingUserLocationWithCourse(zoom: newZoom, pitch: pitch, pitchRange: pitchRange) case .rect: return case .showcase: @@ -33,17 +34,23 @@ public extension MapViewCamera { /// - Parameter newZoom: The value to increment the zoom by. Negative decrements the value. mutating func incrementZoom(by increment: Double) { switch state { - case let .centered(onCoordinate, zoom, pitch, direction): + case let .centered(onCoordinate, zoom, pitch, pitchRange, direction): state = .centered(onCoordinate: onCoordinate, zoom: zoom + increment, pitch: pitch, + pitchRange: pitchRange, direction: direction) - case let .trackingUserLocation(zoom, pitch, direction): - state = .trackingUserLocation(zoom: zoom + increment, pitch: pitch, direction: direction) - case let .trackingUserLocationWithHeading(zoom, pitch): - state = .trackingUserLocationWithHeading(zoom: zoom + increment, pitch: pitch) - case let .trackingUserLocationWithCourse(zoom, pitch): - state = .trackingUserLocationWithCourse(zoom: zoom + increment, pitch: pitch) + case let .trackingUserLocation(zoom, pitch, pitchRange, direction): + state = .trackingUserLocation( + zoom: zoom + increment, + pitch: pitch, + pitchRange: pitchRange, + direction: direction + ) + case let .trackingUserLocationWithHeading(zoom, pitch, pitchRange): + state = .trackingUserLocationWithHeading(zoom: zoom + increment, pitch: pitch, pitchRange: pitchRange) + case let .trackingUserLocationWithCourse(zoom, pitch, pitchRange): + state = .trackingUserLocationWithCourse(zoom: zoom + increment, pitch: pitch, pitchRange: pitchRange) case .rect: return case .showcase: @@ -58,19 +65,20 @@ public extension MapViewCamera { /// Set a new pitch for the current camera state. /// /// - Parameter newPitch: The new pitch value. - mutating func setPitch(_ newPitch: CameraPitch) { + mutating func setPitch(_ newPitch: Double) { switch state { - case let .centered(onCoordinate, zoom, _, direction): + case let .centered(onCoordinate, zoom, _, pitchRange, direction): state = .centered(onCoordinate: onCoordinate, zoom: zoom, pitch: newPitch, + pitchRange: pitchRange, direction: direction) - case let .trackingUserLocation(zoom, _, direction): - state = .trackingUserLocation(zoom: zoom, pitch: newPitch, direction: direction) - case let .trackingUserLocationWithHeading(zoom, _): - state = .trackingUserLocationWithHeading(zoom: zoom, pitch: newPitch) - case let .trackingUserLocationWithCourse(zoom, _): - state = .trackingUserLocationWithCourse(zoom: zoom, pitch: newPitch) + case let .trackingUserLocation(zoom, _, pitchRange, direction): + state = .trackingUserLocation(zoom: zoom, pitch: newPitch, pitchRange: pitchRange, direction: direction) + case let .trackingUserLocationWithHeading(zoom, _, pitchRange): + state = .trackingUserLocationWithHeading(zoom: zoom, pitch: newPitch, pitchRange: pitchRange) + case let .trackingUserLocationWithCourse(zoom, _, pitchRange): + state = .trackingUserLocationWithCourse(zoom: zoom, pitch: newPitch, pitchRange: pitchRange) case .rect: return case .showcase: diff --git a/Sources/MapLibreSwiftUI/MapViewCoordinator.swift b/Sources/MapLibreSwiftUI/MapViewCoordinator.swift index a2f5446..d0886ed 100644 --- a/Sources/MapLibreSwiftUI/MapViewCoordinator.swift +++ b/Sources/MapLibreSwiftUI/MapViewCoordinator.swift @@ -63,33 +63,126 @@ public class MapViewCoordinator: NSObject { } switch camera.state { - case let .centered(onCoordinate: coordinate, zoom: zoom, pitch: pitch, direction: direction): + case let .centered( + onCoordinate: coordinate, + zoom: zoom, + pitch: pitch, + pitchRange: pitchRange, + direction: direction + ): mapView.userTrackingMode = .none - mapView.setCenter(coordinate, - zoomLevel: zoom, - direction: direction, - animated: animated) - mapView.minimumPitch = pitch.rangeValue.lowerBound - mapView.maximumPitch = pitch.rangeValue.upperBound - case let .trackingUserLocation(zoom: zoom, pitch: pitch, direction: direction): + + if mapView.frame.size == .zero { + // On init, the mapView's frame is not set up yet, so manipulation via camera is broken, + // so let's do something else instead. + mapView.setCenter(coordinate, + zoomLevel: zoom, + direction: direction, + animated: animated) + + // this is a workaround for no camera - minimum and maximum will be reset below, but this adjusts it. + mapView.minimumPitch = pitch + mapView.maximumPitch = pitch + + } else { + let camera = mapView.camera + camera.centerCoordinate = coordinate + camera.heading = direction + camera.pitch = pitch + + let altitude = MLNAltitudeForZoomLevel(zoom, pitch, coordinate.latitude, mapView.frame.size) + camera.altitude = altitude + mapView.setCamera(camera, animated: animated) + } + + mapView.minimumPitch = pitchRange.rangeValue.lowerBound + mapView.maximumPitch = pitchRange.rangeValue.upperBound + case let .trackingUserLocation(zoom: zoom, pitch: pitch, pitchRange: pitchRange, direction: direction): mapView.userTrackingMode = .follow - // Needs to be non-animated or else it messes up following - mapView.setZoomLevel(zoom, animated: false) - mapView.direction = direction - mapView.minimumPitch = pitch.rangeValue.lowerBound - mapView.maximumPitch = pitch.rangeValue.upperBound - case let .trackingUserLocationWithHeading(zoom: zoom, pitch: pitch): + + if mapView.frame.size == .zero { + // On init, the mapView's frame is not set up yet, so manipulation via camera is broken, + // so let's do something else instead. + // Needs to be non-animated or else it messes up following + + mapView.setZoomLevel(zoom, animated: false) + mapView.direction = direction + + mapView.minimumPitch = pitch + mapView.maximumPitch = pitch + + } else { + let camera = mapView.camera + camera.heading = direction + camera.pitch = pitch + + let altitude = MLNAltitudeForZoomLevel( + zoom, + pitch, + mapView.camera.centerCoordinate.latitude, + mapView.frame.size + ) + camera.altitude = altitude + mapView.setCamera(camera, animated: animated) + } + mapView.minimumPitch = pitchRange.rangeValue.lowerBound + mapView.maximumPitch = pitchRange.rangeValue.upperBound + case let .trackingUserLocationWithHeading(zoom: zoom, pitch: pitch, pitchRange: pitchRange): mapView.userTrackingMode = .followWithHeading - // Needs to be non-animated or else it messes up following - mapView.setZoomLevel(zoom, animated: false) - mapView.minimumPitch = pitch.rangeValue.lowerBound - mapView.maximumPitch = pitch.rangeValue.upperBound - case let .trackingUserLocationWithCourse(zoom: zoom, pitch: pitch): + + if mapView.frame.size == .zero { + // On init, the mapView's frame is not set up yet, so manipulation via camera is broken, + // so let's do something else instead. + // Needs to be non-animated or else it messes up following + + mapView.setZoomLevel(zoom, animated: false) + mapView.minimumPitch = pitch + mapView.maximumPitch = pitch + + } else { + let camera = mapView.camera + + let altitude = MLNAltitudeForZoomLevel( + zoom, + pitch, + mapView.camera.centerCoordinate.latitude, + mapView.frame.size + ) + camera.altitude = altitude + camera.pitch = pitch + mapView.setCamera(camera, animated: animated) + } + + mapView.minimumPitch = pitchRange.rangeValue.lowerBound + mapView.maximumPitch = pitchRange.rangeValue.upperBound + case let .trackingUserLocationWithCourse(zoom: zoom, pitch: pitch, pitchRange: pitchRange): mapView.userTrackingMode = .followWithCourse - // Needs to be non-animated or else it messes up following - mapView.setZoomLevel(zoom, animated: false) - mapView.minimumPitch = pitch.rangeValue.lowerBound - mapView.maximumPitch = pitch.rangeValue.upperBound + + if mapView.frame.size == .zero { + // On init, the mapView's frame is not set up yet, so manipulation via camera is broken, + // so let's do something else instead. + // Needs to be non-animated or else it messes up following + + mapView.setZoomLevel(zoom, animated: false) + mapView.minimumPitch = pitch + mapView.maximumPitch = pitch + + } else { + let camera = mapView.camera + + let altitude = MLNAltitudeForZoomLevel( + zoom, + pitch, + mapView.camera.centerCoordinate.latitude, + mapView.frame.size + ) + camera.altitude = altitude + camera.pitch = pitch + mapView.setCamera(camera, animated: animated) + } + + mapView.minimumPitch = pitchRange.rangeValue.lowerBound + mapView.maximumPitch = pitchRange.rangeValue.upperBound case let .rect(boundingBox, padding): mapView.setVisibleCoordinateBounds(boundingBox, edgePadding: padding, @@ -244,8 +337,8 @@ extension MapViewCoordinator: MLNMapViewDelegate { // state propagation. let newCamera: MapViewCamera = .center(mapView.centerCoordinate, zoom: mapView.zoomLevel, - // TODO: Pitch doesn't really describe current state - pitch: .freeWithinRange( + pitch: mapView.camera.pitch, + pitchRange: .freeWithinRange( minimum: mapView.minimumPitch, maximum: mapView.maximumPitch ), diff --git a/Sources/MapLibreSwiftUI/Models/MapCamera/CameraPitch.swift b/Sources/MapLibreSwiftUI/Models/MapCamera/CameraPitchRange.swift similarity index 94% rename from Sources/MapLibreSwiftUI/Models/MapCamera/CameraPitch.swift rename to Sources/MapLibreSwiftUI/Models/MapCamera/CameraPitchRange.swift index 45d3cbd..9379339 100644 --- a/Sources/MapLibreSwiftUI/Models/MapCamera/CameraPitch.swift +++ b/Sources/MapLibreSwiftUI/Models/MapCamera/CameraPitchRange.swift @@ -2,7 +2,7 @@ import Foundation import MapLibre /// The current pitch state for the MapViewCamera -public enum CameraPitch: Hashable, Sendable { +public enum CameraPitchRange: Hashable, Sendable { /// The user is free to control pitch from it's default min to max. case free diff --git a/Sources/MapLibreSwiftUI/Models/MapCamera/CameraState.swift b/Sources/MapLibreSwiftUI/Models/MapCamera/CameraState.swift index a62883c..6a09a9a 100644 --- a/Sources/MapLibreSwiftUI/Models/MapCamera/CameraState.swift +++ b/Sources/MapLibreSwiftUI/Models/MapCamera/CameraState.swift @@ -7,7 +7,8 @@ public enum CameraState: Hashable { case centered( onCoordinate: CLLocationCoordinate2D, zoom: Double, - pitch: CameraPitch, + pitch: Double, + pitchRange: CameraPitchRange, direction: CLLocationDirection ) @@ -15,19 +16,19 @@ public enum CameraState: Hashable { /// /// This feature uses the MLNMapView's userTrackingMode to .follow which automatically /// follows the user from within the MLNMapView. - case trackingUserLocation(zoom: Double, pitch: CameraPitch, direction: CLLocationDirection) + case trackingUserLocation(zoom: Double, pitch: Double, pitchRange: CameraPitchRange, direction: CLLocationDirection) /// Follow the user's location using the MapView's internal camera with the user's heading. /// /// This feature uses the MLNMapView's userTrackingMode to .followWithHeading which automatically /// follows the user from within the MLNMapView. - case trackingUserLocationWithHeading(zoom: Double, pitch: CameraPitch) + case trackingUserLocationWithHeading(zoom: Double, pitch: Double, pitchRange: CameraPitchRange) /// Follow the user's location using the MapView's internal camera with the users' course /// /// This feature uses the MLNMapView's userTrackingMode to .followWithCourse which automatically /// follows the user from within the MLNMapView. - case trackingUserLocationWithCourse(zoom: Double, pitch: CameraPitch) + case trackingUserLocationWithCourse(zoom: Double, pitch: Double, pitchRange: CameraPitchRange) /// Centered on a bounding box/rectangle. case rect( @@ -42,8 +43,14 @@ public enum CameraState: Hashable { extension CameraState: CustomDebugStringConvertible { public var debugDescription: String { switch self { - case let .centered(onCoordinate: coordinate, zoom: zoom, pitch: pitch, direction: direction): - "CameraState.centered(onCoordinate: \(coordinate), zoom: \(zoom), pitch: \(pitch), direction: \(direction))" + case let .centered( + onCoordinate: coordinate, + zoom: zoom, + pitch: pitch, + pitchRange: pitchRange, + direction: direction + ): + "CameraState.centered(onCoordinate: \(coordinate), zoom: \(zoom), pitch: \(pitch), pitchRange: \(pitchRange), direction: \(direction))" case let .trackingUserLocation(zoom: zoom): "CameraState.trackingUserLocation(zoom: \(zoom))" case let .trackingUserLocationWithHeading(zoom: zoom): diff --git a/Sources/MapLibreSwiftUI/Models/MapCamera/MapViewCamera.swift b/Sources/MapLibreSwiftUI/Models/MapCamera/MapViewCamera.swift index f9d0f68..f6181af 100644 --- a/Sources/MapLibreSwiftUI/Models/MapCamera/MapViewCamera.swift +++ b/Sources/MapLibreSwiftUI/Models/MapCamera/MapViewCamera.swift @@ -9,7 +9,8 @@ public struct MapViewCamera: Hashable { public enum Defaults { public static let coordinate = CLLocationCoordinate2D(latitude: 0, longitude: 0) public static let zoom: Double = 10 - public static let pitch: CameraPitch = .free + public static let pitch: Double = 0 + public static let pitchRange: CameraPitchRange = .free public static let direction: CLLocationDirection = 0 } @@ -32,6 +33,7 @@ public struct MapViewCamera: Hashable { onCoordinate: Defaults.coordinate, zoom: Defaults.zoom, pitch: Defaults.pitch, + pitchRange: Defaults.pitchRange, direction: Defaults.direction ), lastReasonForChange: .programmatic @@ -48,12 +50,21 @@ public struct MapViewCamera: Hashable { /// - Returns: The constructed MapViewCamera. public static func center(_ coordinate: CLLocationCoordinate2D, zoom: Double, - pitch: CameraPitch = Defaults.pitch, + pitch: Double = Defaults.pitch, + pitchRange: CameraPitchRange = Defaults.pitchRange, direction: CLLocationDirection = Defaults.direction, reason: CameraChangeReason? = nil) -> MapViewCamera { - MapViewCamera(state: .centered(onCoordinate: coordinate, zoom: zoom, pitch: pitch, direction: direction), - lastReasonForChange: reason) + MapViewCamera( + state: .centered( + onCoordinate: coordinate, + zoom: zoom, + pitch: pitch, + pitchRange: pitchRange, + direction: direction + ), + lastReasonForChange: reason + ) } /// Enables user location tracking within the MapView. @@ -66,12 +77,15 @@ public struct MapViewCamera: Hashable { /// - pitch: Set the camera pitch method. /// - Returns: The MapViewCamera representing the scenario public static func trackUserLocation(zoom: Double = Defaults.zoom, - pitch: CameraPitch = Defaults.pitch, + pitch: Double = Defaults.pitch, + pitchRange: CameraPitchRange = Defaults.pitchRange, direction: CLLocationDirection = Defaults.direction) -> MapViewCamera { // Coordinate is ignored when tracking user location. However, pitch and zoom are valid. - MapViewCamera(state: .trackingUserLocation(zoom: zoom, pitch: pitch, direction: direction), - lastReasonForChange: .programmatic) + MapViewCamera( + state: .trackingUserLocation(zoom: zoom, pitch: pitch, pitchRange: pitchRange, direction: direction), + lastReasonForChange: .programmatic + ) } /// Enables user location tracking within the MapView. @@ -83,11 +97,13 @@ public struct MapViewCamera: Hashable { /// pitch. /// - pitch: Set the camera pitch method. /// - Returns: The MapViewCamera representing the scenario - public static func trackUserLocationWithHeading(zoom: Double = Defaults.zoom, - pitch: CameraPitch = Defaults.pitch) -> MapViewCamera - { + public static func trackUserLocationWithHeading( + zoom: Double = Defaults.zoom, + pitch: Double = Defaults.pitch, + pitchRange: CameraPitchRange = Defaults.pitchRange + ) -> MapViewCamera { // Coordinate is ignored when tracking user location. However, pitch and zoom are valid. - MapViewCamera(state: .trackingUserLocationWithHeading(zoom: zoom, pitch: pitch), + MapViewCamera(state: .trackingUserLocationWithHeading(zoom: zoom, pitch: pitch, pitchRange: pitchRange), lastReasonForChange: .programmatic) } @@ -100,11 +116,13 @@ public struct MapViewCamera: Hashable { /// pitch. /// - pitch: Set the camera pitch method. /// - Returns: The MapViewCamera representing the scenario - public static func trackUserLocationWithCourse(zoom: Double = Defaults.zoom, - pitch: CameraPitch = Defaults.pitch) -> MapViewCamera - { + public static func trackUserLocationWithCourse( + zoom: Double = Defaults.zoom, + pitch: Double = Defaults.pitch, + pitchRange: CameraPitchRange = Defaults.pitchRange + ) -> MapViewCamera { // Coordinate is ignored when tracking user location. However, pitch and zoom are valid. - MapViewCamera(state: .trackingUserLocationWithCourse(zoom: zoom, pitch: pitch), + MapViewCamera(state: .trackingUserLocationWithCourse(zoom: zoom, pitch: pitch, pitchRange: pitchRange), lastReasonForChange: .programmatic) } diff --git a/Tests/MapLibreSwiftUITests/MapViewCoordinator/MapViewCoordinatorCameraTests.swift b/Tests/MapLibreSwiftUITests/MapViewCoordinator/MapViewCoordinatorCameraTests.swift index adbab17..aad4f21 100644 --- a/Tests/MapLibreSwiftUITests/MapViewCoordinator/MapViewCoordinatorCameraTests.swift +++ b/Tests/MapLibreSwiftUITests/MapViewCoordinator/MapViewCoordinatorCameraTests.swift @@ -10,6 +10,7 @@ final class MapViewCoordinatorCameraTests: XCTestCase { override func setUp() async throws { maplibreMapView = MockMLNMapViewCameraUpdating() + given(maplibreMapView).frame.willReturn(.zero) mapView = MapView(styleURL: URL(string: "https://maplibre.org")!) coordinator = MapView.Coordinator(parent: mapView) { _, _ in // No action @@ -39,8 +40,14 @@ final class MapViewCoordinatorCameraTests: XCTestCase { animated: .value(false)) .called(count: 1) + // Due to the .frame == .zero workaround, min/max pitch setting is called twice, once to set the + // pitch, and then once to set the actual range. verify(maplibreMapView) .minimumPitch(newValue: .value(0)) + .setterCalled(count: 2) + + verify(maplibreMapView) + .maximumPitch(newValue: .value(0)) .setterCalled(count: 1) verify(maplibreMapView) @@ -69,8 +76,14 @@ final class MapViewCoordinatorCameraTests: XCTestCase { animated: .value(false)) .called(count: 1) + // Due to the .frame == .zero workaround, min/max pitch setting is called twice, once to set the + // pitch, and then once to set the actual range. verify(maplibreMapView) .minimumPitch(newValue: .value(0)) + .setterCalled(count: 2) + + verify(maplibreMapView) + .maximumPitch(newValue: .value(0)) .setterCalled(count: 1) verify(maplibreMapView) @@ -98,8 +111,14 @@ final class MapViewCoordinatorCameraTests: XCTestCase { animated: .any) .called(count: 0) + // Due to the .frame == .zero workaround, min/max pitch setting is called twice, once to set the + // pitch, and then once to set the actual range. verify(maplibreMapView) .minimumPitch(newValue: .value(0)) + .setterCalled(count: 2) + + verify(maplibreMapView) + .maximumPitch(newValue: .value(0)) .setterCalled(count: 1) verify(maplibreMapView) @@ -127,8 +146,14 @@ final class MapViewCoordinatorCameraTests: XCTestCase { animated: .any) .called(count: 0) + // Due to the .frame == .zero workaround, min/max pitch setting is called twice, once to set the + // pitch, and then once to set the actual range. verify(maplibreMapView) .minimumPitch(newValue: .value(0)) + .setterCalled(count: 2) + + verify(maplibreMapView) + .maximumPitch(newValue: .value(0)) .setterCalled(count: 1) verify(maplibreMapView) @@ -156,8 +181,14 @@ final class MapViewCoordinatorCameraTests: XCTestCase { animated: .any) .called(count: 0) + // Due to the .frame == .zero workaround, min/max pitch setting is called twice, once to set the + // pitch, and then once to set the actual range. verify(maplibreMapView) .minimumPitch(newValue: .value(0)) + .setterCalled(count: 2) + + verify(maplibreMapView) + .maximumPitch(newValue: .value(0)) .setterCalled(count: 1) verify(maplibreMapView) diff --git a/Tests/MapLibreSwiftUITests/Models/MapCamera/CameraPitchTests.swift b/Tests/MapLibreSwiftUITests/Models/MapCamera/CameraPitchTests.swift index 1cb717f..f3e0c34 100644 --- a/Tests/MapLibreSwiftUITests/Models/MapCamera/CameraPitchTests.swift +++ b/Tests/MapLibreSwiftUITests/Models/MapCamera/CameraPitchTests.swift @@ -3,19 +3,19 @@ import XCTest final class CameraPitchTests: XCTestCase { func testFreePitch() { - let pitch: CameraPitch = .free + let pitch: CameraPitchRange = .free XCTAssertEqual(pitch.rangeValue.lowerBound, 0) XCTAssertEqual(pitch.rangeValue.upperBound, 60) } func testRangePitch() { - let pitch = CameraPitch.freeWithinRange(minimum: 9, maximum: 29) + let pitch = CameraPitchRange.freeWithinRange(minimum: 9, maximum: 29) XCTAssertEqual(pitch.rangeValue.lowerBound, 9) XCTAssertEqual(pitch.rangeValue.upperBound, 29) } func testFixedPitch() { - let pitch = CameraPitch.fixed(41) + let pitch = CameraPitchRange.fixed(41) XCTAssertEqual(pitch.rangeValue.lowerBound, 41) XCTAssertEqual(pitch.rangeValue.upperBound, 41) } diff --git a/Tests/MapLibreSwiftUITests/Models/MapCamera/CameraStateTests.swift b/Tests/MapLibreSwiftUITests/Models/MapCamera/CameraStateTests.swift index 514cc70..0fb061e 100644 --- a/Tests/MapLibreSwiftUITests/Models/MapCamera/CameraStateTests.swift +++ b/Tests/MapLibreSwiftUITests/Models/MapCamera/CameraStateTests.swift @@ -7,26 +7,32 @@ final class CameraStateTests: XCTestCase { let coordinate = CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4) func testCenterCameraState() { - let state: CameraState = .centered(onCoordinate: coordinate, zoom: 4, pitch: .free, direction: 42) - XCTAssertEqual(state, .centered(onCoordinate: coordinate, zoom: 4, pitch: .free, direction: 42)) + let state: CameraState = .centered( + onCoordinate: coordinate, + zoom: 4, + pitch: 0, + pitchRange: .free, + direction: 42 + ) + XCTAssertEqual(state, .centered(onCoordinate: coordinate, zoom: 4, pitch: 0, pitchRange: .free, direction: 42)) assertSnapshot(of: state, as: .description) } func testTrackingUserLocation() { - let state: CameraState = .trackingUserLocation(zoom: 4, pitch: .free, direction: 12) - XCTAssertEqual(state, .trackingUserLocation(zoom: 4, pitch: .free, direction: 12)) + let state: CameraState = .trackingUserLocation(zoom: 4, pitch: 0, pitchRange: .free, direction: 12) + XCTAssertEqual(state, .trackingUserLocation(zoom: 4, pitch: 0, pitchRange: .free, direction: 12)) assertSnapshot(of: state, as: .description) } func testTrackingUserLocationWithHeading() { - let state: CameraState = .trackingUserLocationWithHeading(zoom: 4, pitch: .free) - XCTAssertEqual(state, .trackingUserLocationWithHeading(zoom: 4, pitch: .free)) + let state: CameraState = .trackingUserLocationWithHeading(zoom: 4, pitch: 0, pitchRange: .free) + XCTAssertEqual(state, .trackingUserLocationWithHeading(zoom: 4, pitch: 0, pitchRange: .free)) assertSnapshot(of: state, as: .description) } func testTrackingUserLocationWithCourse() { - let state: CameraState = .trackingUserLocationWithCourse(zoom: 4, pitch: .free) - XCTAssertEqual(state, .trackingUserLocationWithCourse(zoom: 4, pitch: .free)) + let state: CameraState = .trackingUserLocationWithCourse(zoom: 4, pitch: 0, pitchRange: .free) + XCTAssertEqual(state, .trackingUserLocationWithCourse(zoom: 4, pitch: 0, pitchRange: .free)) assertSnapshot(of: state, as: .description) } diff --git a/Tests/MapLibreSwiftUITests/Models/MapCamera/MapViewCameraTests.swift b/Tests/MapLibreSwiftUITests/Models/MapCamera/MapViewCameraTests.swift index 31b0cad..4f01c41 100644 --- a/Tests/MapLibreSwiftUITests/Models/MapCamera/MapViewCameraTests.swift +++ b/Tests/MapLibreSwiftUITests/Models/MapCamera/MapViewCameraTests.swift @@ -9,7 +9,7 @@ final class MapViewCameraTests: XCTestCase { let camera = MapViewCamera.center( CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4), zoom: 5, - pitch: .freeWithinRange(minimum: 12, maximum: 34), + pitch: 12, direction: 23 ) @@ -17,21 +17,21 @@ final class MapViewCameraTests: XCTestCase { } func testTrackingUserLocation() { - let pitch: CameraPitch = .freeWithinRange(minimum: 12, maximum: 34) - let camera = MapViewCamera.trackUserLocation(zoom: 10, pitch: pitch) + let pitch: CameraPitchRange = .freeWithinRange(minimum: 12, maximum: 34) + let camera = MapViewCamera.trackUserLocation(zoom: 10, pitchRange: pitch) assertSnapshot(of: camera, as: .dump) } func testTrackUserLocationWithCourse() { - let pitch: CameraPitch = .freeWithinRange(minimum: 12, maximum: 34) - let camera = MapViewCamera.trackUserLocationWithCourse(zoom: 18, pitch: pitch) + let pitchRange: CameraPitchRange = .freeWithinRange(minimum: 12, maximum: 34) + let camera = MapViewCamera.trackUserLocationWithCourse(zoom: 18, pitchRange: pitchRange) assertSnapshot(of: camera, as: .dump) } func testTrackUserLocationWithHeading() { - let camera = MapViewCamera.trackUserLocationWithHeading(zoom: 10, pitch: .free) + let camera = MapViewCamera.trackUserLocationWithHeading(zoom: 10, pitch: 0) assertSnapshot(of: camera, as: .dump) } diff --git a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testCenterCameraState.1.txt b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testCenterCameraState.1.txt index 6d2f3a0..1f4c7be 100644 --- a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testCenterCameraState.1.txt +++ b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testCenterCameraState.1.txt @@ -1 +1 @@ -CameraState.centered(onCoordinate: CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4), zoom: 4.0, pitch: free, direction: 42.0) \ No newline at end of file +CameraState.centered(onCoordinate: CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4), zoom: 4.0, pitch: 0.0, pitchRange: free, direction: 42.0) \ No newline at end of file diff --git a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocation.1.txt b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocation.1.txt index 8cf7a75..0955934 100644 --- a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocation.1.txt +++ b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocation.1.txt @@ -1 +1 @@ -CameraState.trackingUserLocation(zoom: (4.0, MapLibreSwiftUI.CameraPitch.free, 12.0)) \ No newline at end of file +CameraState.trackingUserLocation(zoom: (4.0, 0.0, MapLibreSwiftUI.CameraPitchRange.free, 12.0)) \ No newline at end of file diff --git a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocationWithCourse.1.txt b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocationWithCourse.1.txt index 639e899..13eace7 100644 --- a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocationWithCourse.1.txt +++ b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocationWithCourse.1.txt @@ -1 +1 @@ -CameraState.trackingUserLocationWithCourse(zoom: (4.0, MapLibreSwiftUI.CameraPitch.free)) \ No newline at end of file +CameraState.trackingUserLocationWithCourse(zoom: (4.0, 0.0, MapLibreSwiftUI.CameraPitchRange.free)) \ No newline at end of file diff --git a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocationWithHeading.1.txt b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocationWithHeading.1.txt index 13adebe..a071639 100644 --- a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocationWithHeading.1.txt +++ b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocationWithHeading.1.txt @@ -1 +1 @@ -CameraState.trackingUserLocationWithHeading(zoom: (4.0, MapLibreSwiftUI.CameraPitch.free)) \ No newline at end of file +CameraState.trackingUserLocationWithHeading(zoom: (4.0, 0.0, MapLibreSwiftUI.CameraPitchRange.free)) \ No newline at end of file diff --git a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testCenterCamera.1.txt b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testCenterCamera.1.txt index d3c277d..318c577 100644 --- a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testCenterCamera.1.txt +++ b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testCenterCamera.1.txt @@ -1,13 +1,11 @@ ▿ MapViewCamera - lastReasonForChange: Optional.none ▿ state: CameraState - ▿ centered: (4 elements) + ▿ centered: (5 elements) ▿ onCoordinate: CLLocationCoordinate2D - latitude: 12.3 - longitude: 23.4 - zoom: 5.0 - ▿ pitch: CameraPitch - ▿ freeWithinRange: (2 elements) - - minimum: 12.0 - - maximum: 34.0 + - pitch: 12.0 + - pitchRange: CameraPitchRange.free - direction: 23.0 diff --git a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackUserLocationWithCourse.1.txt b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackUserLocationWithCourse.1.txt index 4c5c2d5..f405f82 100644 --- a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackUserLocationWithCourse.1.txt +++ b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackUserLocationWithCourse.1.txt @@ -2,9 +2,10 @@ ▿ lastReasonForChange: Optional - some: CameraChangeReason.programmatic ▿ state: CameraState - ▿ trackingUserLocationWithCourse: (2 elements) + ▿ trackingUserLocationWithCourse: (3 elements) - zoom: 18.0 - ▿ pitch: CameraPitch + - pitch: 0.0 + ▿ pitchRange: CameraPitchRange ▿ freeWithinRange: (2 elements) - minimum: 12.0 - maximum: 34.0 diff --git a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackUserLocationWithHeading.1.txt b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackUserLocationWithHeading.1.txt index 47942b2..cb5da4b 100644 --- a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackUserLocationWithHeading.1.txt +++ b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackUserLocationWithHeading.1.txt @@ -2,6 +2,7 @@ ▿ lastReasonForChange: Optional - some: CameraChangeReason.programmatic ▿ state: CameraState - ▿ trackingUserLocationWithHeading: (2 elements) + ▿ trackingUserLocationWithHeading: (3 elements) - zoom: 10.0 - - pitch: CameraPitch.free + - pitch: 0.0 + - pitchRange: CameraPitchRange.free diff --git a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackingUserLocation.1.txt b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackingUserLocation.1.txt index 9b728f1..4252dc9 100644 --- a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackingUserLocation.1.txt +++ b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackingUserLocation.1.txt @@ -2,9 +2,10 @@ ▿ lastReasonForChange: Optional - some: CameraChangeReason.programmatic ▿ state: CameraState - ▿ trackingUserLocation: (3 elements) + ▿ trackingUserLocation: (4 elements) - zoom: 10.0 - ▿ pitch: CameraPitch + - pitch: 0.0 + ▿ pitchRange: CameraPitchRange ▿ freeWithinRange: (2 elements) - minimum: 12.0 - maximum: 34.0