Skip to content

Commit

Permalink
Complete refactor of state-specific props out to the state
Browse files Browse the repository at this point in the history
  • Loading branch information
ianthetechie committed Mar 13, 2024
1 parent e13f845 commit 96919e4
Show file tree
Hide file tree
Showing 13 changed files with 67 additions and 71 deletions.
22 changes: 13 additions & 9 deletions Sources/MapLibreSwiftUI/MapViewCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,29 @@ public class MapViewCoordinator: NSObject {
}

switch camera.state {
case let .centered(onCoordinate: coordinate, zoom: zoom):
case let .centered(onCoordinate: coordinate, zoom: zoom, pitch: pitch, direction: direction):
mapView.userTrackingMode = .none
mapView.setCenter(coordinate,
zoomLevel: zoom,
direction: camera.direction,
direction: direction,
animated: animated)
case let .trackingUserLocation(zoom: zoom):
mapView.minimumPitch = pitch.rangeValue.lowerBound
mapView.maximumPitch = pitch.rangeValue.upperBound
case let .trackingUserLocation(zoom: zoom, pitch: pitch):
mapView.userTrackingMode = .follow
mapView.setZoomLevel(zoom, animated: animated)
case let .trackingUserLocationWithHeading(zoom: zoom):
mapView.minimumPitch = pitch.rangeValue.lowerBound
mapView.maximumPitch = pitch.rangeValue.upperBound
case let .trackingUserLocationWithHeading(zoom: zoom, pitch: pitch):
mapView.userTrackingMode = .followWithHeading
mapView.setZoomLevel(zoom, animated: animated)
case let .trackingUserLocationWithCourse(zoom: zoom):
mapView.minimumPitch = pitch.rangeValue.lowerBound
mapView.maximumPitch = pitch.rangeValue.upperBound
case let .trackingUserLocationWithCourse(zoom: zoom, pitch: pitch):
mapView.userTrackingMode = .followWithCourse
mapView.setZoomLevel(zoom, animated: animated)
mapView.minimumPitch = pitch.rangeValue.lowerBound
mapView.maximumPitch = pitch.rangeValue.upperBound
case let .rect(boundingBox, padding):
mapView.setVisibleCoordinateBounds(boundingBox,
edgePadding: padding,
Expand All @@ -73,10 +81,6 @@ public class MapViewCoordinator: NSObject {
break
}

// Set the correct pitch range.
mapView.minimumPitch = camera.pitch.rangeValue.lowerBound
mapView.maximumPitch = camera.pitch.rangeValue.upperBound

snapshotCamera = camera
}

Expand Down
17 changes: 11 additions & 6 deletions Sources/MapLibreSwiftUI/Models/MapCamera/CameraState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,30 @@ import MapLibre
/// The CameraState is used to understand the current context of the MapView's camera.
public enum CameraState: Hashable {
/// Centered on a coordinate
case centered(onCoordinate: CLLocationCoordinate2D, zoom: Double)
case centered(
onCoordinate: CLLocationCoordinate2D,
zoom: Double,
pitch: CameraPitch,
direction: CLLocationDirection
)

/// Follow the user's location using the MapView's internal camera.
///
/// This feature uses the MLNMapView's userTrackingMode to .follow which automatically
/// follows the user from within the MLNMapView.
case trackingUserLocation(zoom: Double)
case trackingUserLocation(zoom: Double, pitch: CameraPitch)

/// 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)
case trackingUserLocationWithHeading(zoom: Double, pitch: CameraPitch)

/// 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)
case trackingUserLocationWithCourse(zoom: Double, pitch: CameraPitch)

/// Centered on a bounding box/rectangle.
case rect(
Expand All @@ -37,8 +42,8 @@ public enum CameraState: Hashable {
extension CameraState: CustomDebugStringConvertible {
public var debugDescription: String {
switch self {
case let .centered(onCoordinate: coordinate, zoom: zoom):
"CameraState.centered(onCoordinate: \(coordinate), zoom: \(zoom))"
case let .centered(onCoordinate: coordinate, zoom: zoom, pitch: pitch, direction: direction):
"CameraState.centered(onCoordinate: \(coordinate), zoom: \(zoom), pitch: \(pitch), direction: \(direction))"
case let .trackingUserLocation(zoom: zoom):
"CameraState.trackingUserLocation(zoom: \(zoom))"
case let .trackingUserLocationWithHeading(zoom: zoom):
Expand Down
34 changes: 13 additions & 21 deletions Sources/MapLibreSwiftUI/Models/MapCamera/MapViewCamera.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ public struct MapViewCamera: Hashable {
}

public var state: CameraState
public var pitch: CameraPitch
public var direction: CLLocationDirection

/// The reason the camera was changed.
///
Expand All @@ -29,10 +27,15 @@ public struct MapViewCamera: Hashable {
///
/// - Returns: The constructed MapViewCamera.
public static func `default`() -> MapViewCamera {
MapViewCamera(state: .centered(onCoordinate: Defaults.coordinate, zoom: Defaults.zoom),
pitch: Defaults.pitch,
direction: Defaults.direction,
lastReasonForChange: .programmatic)
MapViewCamera(
state: .centered(
onCoordinate: Defaults.coordinate,
zoom: Defaults.zoom,
pitch: Defaults.pitch,
direction: Defaults.direction
),
lastReasonForChange: .programmatic
)
}

/// Center the map on a specific location.
Expand All @@ -49,9 +52,7 @@ public struct MapViewCamera: Hashable {
direction: CLLocationDirection = Defaults.direction,
reason: CameraChangeReason? = nil) -> MapViewCamera
{
MapViewCamera(state: .centered(onCoordinate: coordinate, zoom: zoom),
pitch: pitch,
direction: direction,
MapViewCamera(state: .centered(onCoordinate: coordinate, zoom: zoom, pitch: pitch, direction: direction),
lastReasonForChange: reason)
}

Expand All @@ -68,9 +69,7 @@ public struct MapViewCamera: Hashable {
pitch: CameraPitch = Defaults.pitch) -> MapViewCamera
{
// Coordinate is ignored when tracking user location. However, pitch and zoom are valid.
MapViewCamera(state: .trackingUserLocation(zoom: zoom),
pitch: pitch,
direction: Defaults.direction,
MapViewCamera(state: .trackingUserLocation(zoom: zoom, pitch: pitch),
lastReasonForChange: .programmatic)
}

Expand All @@ -87,9 +86,7 @@ public struct MapViewCamera: Hashable {
pitch: CameraPitch = Defaults.pitch) -> MapViewCamera
{
// Coordinate is ignored when tracking user location. However, pitch and zoom are valid.
MapViewCamera(state: .trackingUserLocationWithHeading(zoom: zoom),
pitch: pitch,
direction: Defaults.direction,
MapViewCamera(state: .trackingUserLocationWithHeading(zoom: zoom, pitch: pitch),
lastReasonForChange: .programmatic)
}

Expand All @@ -106,9 +103,7 @@ public struct MapViewCamera: Hashable {
pitch: CameraPitch = Defaults.pitch) -> MapViewCamera
{
// Coordinate is ignored when tracking user location. However, pitch and zoom are valid.
MapViewCamera(state: .trackingUserLocationWithCourse(zoom: zoom),
pitch: pitch,
direction: Defaults.direction,
MapViewCamera(state: .trackingUserLocationWithCourse(zoom: zoom, pitch: pitch),
lastReasonForChange: .programmatic)
}

Expand All @@ -122,10 +117,7 @@ public struct MapViewCamera: Hashable {
_ box: MLNCoordinateBounds,
edgePadding: UIEdgeInsets = .init(top: 20, left: 20, bottom: 20, right: 20)
) -> MapViewCamera {
// TODO: pitch & direction are ignored. We should extract these into the state parameter.
MapViewCamera(state: .rect(boundingBox: box, edgePadding: edgePadding),
pitch: Defaults.pitch,
direction: Defaults.direction,
lastReasonForChange: .programmatic)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,26 @@ final class CameraStateTests: XCTestCase {
let coordinate = CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4)

func testCenterCameraState() {
let state: CameraState = .centered(onCoordinate: coordinate, zoom: 4)
XCTAssertEqual(state, .centered(onCoordinate: coordinate, zoom: 4))
let state: CameraState = .centered(onCoordinate: coordinate, zoom: 4, pitch: .free, direction: 42)
XCTAssertEqual(state, .centered(onCoordinate: coordinate, zoom: 4, pitch: .free, direction: 42))
assertSnapshot(of: state, as: .description)
}

func testTrackingUserLocation() {
let state: CameraState = .trackingUserLocation(zoom: 4)
XCTAssertEqual(state, .trackingUserLocation(zoom: 4))
let state: CameraState = .trackingUserLocation(zoom: 4, pitch: .free)
XCTAssertEqual(state, .trackingUserLocation(zoom: 4, pitch: .free))
assertSnapshot(of: state, as: .description)
}

func testTrackingUserLocationWithHeading() {
let state: CameraState = .trackingUserLocationWithHeading(zoom: 4)
XCTAssertEqual(state, .trackingUserLocationWithHeading(zoom: 4))
let state: CameraState = .trackingUserLocationWithHeading(zoom: 4, pitch: .free)
XCTAssertEqual(state, .trackingUserLocationWithHeading(zoom: 4, pitch: .free))
assertSnapshot(of: state, as: .description)
}

func testTrackingUserLocationWithCourse() {
let state: CameraState = .trackingUserLocationWithCourse(zoom: 4)
XCTAssertEqual(state, .trackingUserLocationWithCourse(zoom: 4))
let state: CameraState = .trackingUserLocationWithCourse(zoom: 4, pitch: .free)
XCTAssertEqual(state, .trackingUserLocationWithCourse(zoom: 4, pitch: .free))
assertSnapshot(of: state, as: .description)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
CameraState.centered(onCoordinate: CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4), zoom: 4.0)
CameraState.centered(onCoordinate: CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4), zoom: 4.0, pitch: free, direction: 42.0)
Original file line number Diff line number Diff line change
@@ -1 +1 @@
CameraState.trackingUserLocation(zoom: 4.0)
CameraState.trackingUserLocation(zoom: (4.0, MapLibreSwiftUI.CameraPitch.free))
Original file line number Diff line number Diff line change
@@ -1 +1 @@
CameraState.trackingUserLocationWithCourse(zoom: 4.0)
CameraState.trackingUserLocationWithCourse(zoom: (4.0, MapLibreSwiftUI.CameraPitch.free))
Original file line number Diff line number Diff line change
@@ -1 +1 @@
CameraState.trackingUserLocationWithHeading(zoom: 4.0)
CameraState.trackingUserLocationWithHeading(zoom: (4.0, MapLibreSwiftUI.CameraPitch.free))
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
▿ MapViewCamera
- direction: 0.0
▿ lastReasonForChange: Optional<CameraChangeReason>
- some: CameraChangeReason.programmatic
- pitch: CameraPitch.free
▿ state: CameraState
▿ rect: (2 elements)
▿ boundingBox: MLNCoordinateBounds
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
▿ MapViewCamera
- direction: 23.0
- lastReasonForChange: Optional<CameraChangeReason>.none
▿ pitch: CameraPitch
▿ freeWithinRange: (2 elements)
- minimum: 12.0
- maximum: 34.0
▿ state: CameraState
▿ centered: (2 elements)
▿ centered: (4 elements)
▿ onCoordinate: CLLocationCoordinate2D
- latitude: 12.3
- longitude: 23.4
- zoom: 5.0
▿ pitch: CameraPitch
▿ freeWithinRange: (2 elements)
- minimum: 12.0
- maximum: 34.0
- direction: 23.0
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
▿ MapViewCamera
- direction: 0.0
▿ lastReasonForChange: Optional<CameraChangeReason>
- some: CameraChangeReason.programmatic
▿ pitch: CameraPitch
▿ freeWithinRange: (2 elements)
- minimum: 12.0
- maximum: 34.0
▿ state: CameraState
▿ trackingUserLocationWithCourse: (1 element)
▿ trackingUserLocationWithCourse: (2 elements)
- zoom: 18.0
▿ pitch: CameraPitch
▿ freeWithinRange: (2 elements)
- minimum: 12.0
- maximum: 34.0
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
▿ MapViewCamera
- direction: 0.0
▿ lastReasonForChange: Optional<CameraChangeReason>
- some: CameraChangeReason.programmatic
- pitch: CameraPitch.free
▿ state: CameraState
▿ trackingUserLocationWithHeading: (1 element)
▿ trackingUserLocationWithHeading: (2 elements)
- zoom: 10.0
- pitch: CameraPitch.free
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
▿ MapViewCamera
- direction: 0.0
▿ lastReasonForChange: Optional<CameraChangeReason>
- some: CameraChangeReason.programmatic
▿ pitch: CameraPitch
▿ freeWithinRange: (2 elements)
- minimum: 12.0
- maximum: 34.0
▿ state: CameraState
▿ trackingUserLocation: (1 element)
▿ trackingUserLocation: (2 elements)
- zoom: 10.0
▿ pitch: CameraPitch
▿ freeWithinRange: (2 elements)
- minimum: 12.0
- maximum: 34.0

0 comments on commit 96919e4

Please sign in to comment.