diff --git a/Sources/MapLibreSwiftUI/Extensions/MapLibre/MLNMapViewCameraUpdating.swift b/Sources/MapLibreSwiftUI/Extensions/MapLibre/MLNMapViewCameraUpdating.swift index 7568bc2..d7f01a4 100644 --- a/Sources/MapLibreSwiftUI/Extensions/MapLibre/MLNMapViewCameraUpdating.swift +++ b/Sources/MapLibreSwiftUI/Extensions/MapLibre/MLNMapViewCameraUpdating.swift @@ -9,6 +9,7 @@ protocol MLNMapViewCameraUpdating: AnyObject { @MainActor var userTrackingMode: MLNUserTrackingMode { get set } @MainActor var minimumPitch: CGFloat { get set } @MainActor var maximumPitch: CGFloat { get set } + @MainActor var direction: CLLocationDirection { get set } @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 new file mode 100644 index 0000000..cb04a1d --- /dev/null +++ b/Sources/MapLibreSwiftUI/Extensions/MapViewCamera/MapViewCameraOperations.swift @@ -0,0 +1,84 @@ +import Foundation + +public extension MapViewCamera { + // MARK: Zoom + + /// Set a new zoom for the current camera state. + /// + /// - Parameter newZoom: The new zoom value. + mutating func setZoom(_ newZoom: Double) { + switch state { + case let .centered(onCoordinate, _, pitch, direction): + state = .centered(onCoordinate: onCoordinate, + zoom: newZoom, + pitch: pitch, + 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 .rect(boundingBox, edgePadding): + return + case let .showcase(shapeCollection): + return + } + + lastReasonForChange = .programmatic + } + + /// Increment the zoom of the current camera state. + /// + /// - 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): + state = .centered(onCoordinate: onCoordinate, + zoom: zoom + increment, + pitch: pitch, + 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 .rect(boundingBox, edgePadding): + return + case let .showcase(shapeCollection): + return + } + + lastReasonForChange = .programmatic + } + + // MARK: Pitch + + /// Set a new pitch for the current camera state. + /// + /// - Parameter newPitch: The new pitch value. + mutating func setPitch(_ newPitch: CameraPitch) { + switch state { + case let .centered(onCoordinate, zoom, pitch, direction): + state = .centered(onCoordinate: onCoordinate, + zoom: zoom, + pitch: newPitch, + 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 .rect(boundingBox, edgePadding): + return + case let .showcase(shapeCollection): + return + } + + lastReasonForChange = .programmatic + } + + // TODO: Add direction set +} diff --git a/Sources/MapLibreSwiftUI/MapViewCoordinator.swift b/Sources/MapLibreSwiftUI/MapViewCoordinator.swift index d242491..5a23041 100644 --- a/Sources/MapLibreSwiftUI/MapViewCoordinator.swift +++ b/Sources/MapLibreSwiftUI/MapViewCoordinator.swift @@ -67,10 +67,11 @@ public class MapViewCoordinator: NSObject { animated: animated) mapView.minimumPitch = pitch.rangeValue.lowerBound mapView.maximumPitch = pitch.rangeValue.upperBound - case let .trackingUserLocation(zoom: zoom, pitch: pitch): + case let .trackingUserLocation(zoom: zoom, pitch: pitch, 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): diff --git a/Sources/MapLibreSwiftUI/Models/MapCamera/CameraState.swift b/Sources/MapLibreSwiftUI/Models/MapCamera/CameraState.swift index ecb36f2..a62883c 100644 --- a/Sources/MapLibreSwiftUI/Models/MapCamera/CameraState.swift +++ b/Sources/MapLibreSwiftUI/Models/MapCamera/CameraState.swift @@ -15,7 +15,7 @@ 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) + case trackingUserLocation(zoom: Double, pitch: CameraPitch, direction: CLLocationDirection) /// Follow the user's location using the MapView's internal camera with the user's heading. /// diff --git a/Sources/MapLibreSwiftUI/Models/MapCamera/MapViewCamera.swift b/Sources/MapLibreSwiftUI/Models/MapCamera/MapViewCamera.swift index cb1345e..f9d0f68 100644 --- a/Sources/MapLibreSwiftUI/Models/MapCamera/MapViewCamera.swift +++ b/Sources/MapLibreSwiftUI/Models/MapCamera/MapViewCamera.swift @@ -66,10 +66,11 @@ 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) -> MapViewCamera + pitch: CameraPitch = Defaults.pitch, + 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), + MapViewCamera(state: .trackingUserLocation(zoom: zoom, pitch: pitch, direction: direction), lastReasonForChange: .programmatic) } diff --git a/Tests/MapLibreSwiftUITests/Models/MapCamera/CameraStateTests.swift b/Tests/MapLibreSwiftUITests/Models/MapCamera/CameraStateTests.swift index 0bbbae1..514cc70 100644 --- a/Tests/MapLibreSwiftUITests/Models/MapCamera/CameraStateTests.swift +++ b/Tests/MapLibreSwiftUITests/Models/MapCamera/CameraStateTests.swift @@ -13,8 +13,8 @@ final class CameraStateTests: XCTestCase { } func testTrackingUserLocation() { - let state: CameraState = .trackingUserLocation(zoom: 4, pitch: .free) - XCTAssertEqual(state, .trackingUserLocation(zoom: 4, pitch: .free)) + let state: CameraState = .trackingUserLocation(zoom: 4, pitch: .free, direction: 12) + XCTAssertEqual(state, .trackingUserLocation(zoom: 4, pitch: .free, direction: 12)) assertSnapshot(of: state, as: .description) } diff --git a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocation.1.txt b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocation.1.txt index 8c216f7..8cf7a75 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)) \ No newline at end of file +CameraState.trackingUserLocation(zoom: (4.0, MapLibreSwiftUI.CameraPitch.free, 12.0)) \ No newline at end of file diff --git a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackingUserLocation.1.txt b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackingUserLocation.1.txt index 922b6bb..9b728f1 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: (2 elements) + ▿ trackingUserLocation: (3 elements) - zoom: 10.0 ▿ pitch: CameraPitch ▿ freeWithinRange: (2 elements) - minimum: 12.0 - maximum: 34.0 + - direction: 0.0