Skip to content

Commit

Permalink
Minor refactoring; introduce more snapshot tests to make things cleaner
Browse files Browse the repository at this point in the history
  • Loading branch information
ianthetechie committed Mar 12, 2024
1 parent 875aff8 commit c22d05d
Show file tree
Hide file tree
Showing 14 changed files with 103 additions and 63 deletions.
14 changes: 9 additions & 5 deletions Sources/MapLibreSwiftUI/Examples/Camera.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ struct CameraDirectManipulationPreview: View {

let styleURL: URL
var onStyleLoaded: (() -> Void)? = nil
var targetCameraAfterDelay: MapViewCamera? = nil

var body: some View {
MapView(styleURL: styleURL, camera: $camera)
Expand All @@ -16,7 +17,7 @@ struct CameraDirectManipulationPreview: View {
onStyleLoaded?()
}
.overlay(alignment: .bottom, content: {
Text("\(String(describing: camera.state)) z \(camera.zoom)")
Text("\(String(describing: camera.state))")
.padding()
.foregroundColor(.white)
.background(
Expand All @@ -27,16 +28,19 @@ struct CameraDirectManipulationPreview: View {
.padding(.bottom, 42)
})
.task {
try? await Task.sleep(nanoseconds: 3 * NSEC_PER_SEC)
if let targetCameraAfterDelay {
try? await Task.sleep(nanoseconds: 3 * NSEC_PER_SEC)

camera = MapViewCamera.center(switzerland, zoom: 6)
camera = targetCameraAfterDelay
}
}
}
}

#Preview("Camera Preview") {
#Preview("Camera Zoom after delay") {
CameraDirectManipulationPreview(
styleURL: URL(string: "https://demotiles.maplibre.org/style.json")!
styleURL: URL(string: "https://demotiles.maplibre.org/style.json")!,
targetCameraAfterDelay: .center(switzerland, zoom: 6)
)
.ignoresSafeArea(.all)
}
39 changes: 23 additions & 16 deletions Sources/MapLibreSwiftUI/MapViewCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public class MapViewCoordinator: NSObject {
zoomLevel: camera.zoom,
direction: camera.direction,
animated: animated)
case .trackingUserLocation:
case.trackingUserLocation:
mapView.userTrackingMode = .follow
mapView.setZoomLevel(camera.zoom, animated: false)
case .trackingUserLocationWithHeading:
Expand Down Expand Up @@ -176,32 +176,39 @@ public class MapViewCoordinator: NSObject {
// MARK: - MLNMapViewDelegate

extension MapViewCoordinator: MLNMapViewDelegate {
public func mapView(_: MLNMapView, didFinishLoading mglStyle: MLNStyle) {
public func mapView(_ mapView: MLNMapView, didFinishLoading mglStyle: MLNStyle) {
addLayers(to: mglStyle)
onStyleLoaded?(mglStyle)
}

/// The MapView's region has changed with a specific reason.
public func mapView(_ mapView: MLNMapView, regionDidChangeWith reason: MLNCameraChangeReason, animated _: Bool) {
// Validate that the mapView.userTrackingMode still matches our desired camera state for each tracking type.
let isFollowing = parent.camera.state == .trackingUserLocation && mapView.userTrackingMode == .follow
let isFollowingHeading = parent.camera.state == .trackingUserLocationWithHeading && mapView
.userTrackingMode == .followWithHeading
let isFollowingCourse = parent.camera.state == .trackingUserLocationWithCourse && mapView
.userTrackingMode == .followWithCourse

// If any of these are a mismatch, we know the camera is no longer following a desired method, so we should
// detach and revert to a .centered camera. If any one of these is true, the desired camera state still matches
// the mapView's userTrackingMode
if isFollowing || isFollowingHeading || isFollowingCourse {
// User tracking is still active, we can ignore camera updates until we unset/fail this boolean check
// detach and revert to a .centered camera. If any one of these is true, the desired camera state still
// matches the mapView's userTrackingMode
let isProgrammaticallyTracking: Bool = switch parent.camera.state {
case .centered(onCoordinate: _):
false
case .trackingUserLocation:
mapView.userTrackingMode == .follow
case .trackingUserLocationWithHeading:
mapView.userTrackingMode == .followWithHeading
case .trackingUserLocationWithCourse:
mapView.userTrackingMode == .followWithCourse
case .rect(northeast: _, southwest: _):
false
case .showcase(shapeCollection: _):
false
}

if isProgrammaticallyTracking {
// Programmatic tracking is still active, we can ignore camera updates until we unset/fail this boolean
// check
return
}

DispatchQueue.main.async {
// The user's desired camera is not a user tracking method, now we need to publish the MLNMapView's camera
// state
// to the MapView camera binding.
// Publish the MLNMapView's "raw" camera state to the MapView camera binding.
self.parent.camera = .center(mapView.centerCoordinate,
zoom: mapView.zoomLevel,
reason: CameraChangeReason(reason))
Expand Down
4 changes: 2 additions & 2 deletions Sources/MapLibreSwiftUI/Models/MapCamera/CameraState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ public enum CameraState: Hashable {
extension CameraState: CustomDebugStringConvertible {
public var debugDescription: String {
switch self {
case let .centered(onCoordinate: onCoordinate):
"CameraState.centered(onCoordinate: \(onCoordinate)"
case let .centered(onCoordinate: coordinate):
"CameraState.centered(onCoordinate: \(coordinate))"
case .trackingUserLocation:
"CameraState.trackingUserLocation"
case .trackingUserLocationWithHeading:
Expand Down
24 changes: 10 additions & 14 deletions Tests/MapLibreSwiftUITests/Models/MapCamera/CameraStateTests.swift
Original file line number Diff line number Diff line change
@@ -1,34 +1,33 @@
import CoreLocation
import SnapshotTesting
import XCTest
@testable import MapLibreSwiftUI

final class CameraStateTests: XCTestCase {
let coordinate = CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4)

func testCenterCameraState() {
let expectedCoordinate = CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4)
let state: CameraState = .centered(onCoordinate: expectedCoordinate)
XCTAssertEqual(state, .centered(onCoordinate: CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4)))
XCTAssertEqual(
String(describing: state),
"CameraState.centered(onCoordinate: CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4)"
)
let state: CameraState = .centered(onCoordinate: coordinate)
XCTAssertEqual(state, .centered(onCoordinate: coordinate))
assertSnapshot(of: state, as: .description)
}

func testTrackingUserLocation() {
let state: CameraState = .trackingUserLocation
XCTAssertEqual(state, .trackingUserLocation)
XCTAssertEqual(String(describing: state), "CameraState.trackingUserLocation")
assertSnapshot(of: state, as: .description)
}

func testTrackingUserLocationWithHeading() {
let state: CameraState = .trackingUserLocationWithHeading
XCTAssertEqual(state, .trackingUserLocationWithHeading)
XCTAssertEqual(String(describing: state), "CameraState.trackingUserLocationWithHeading")
assertSnapshot(of: state, as: .description)
}

func testTrackingUserLocationWithCourse() {
let state: CameraState = .trackingUserLocationWithCourse
XCTAssertEqual(state, .trackingUserLocationWithCourse)
XCTAssertEqual(String(describing: state), "CameraState.trackingUserLocationWithCourse")
assertSnapshot(of: state, as: .description)
}

func testRect() {
Expand All @@ -37,9 +36,6 @@ final class CameraStateTests: XCTestCase {

let state: CameraState = .rect(northeast: northeast, southwest: southwest)
XCTAssertEqual(state, .rect(northeast: northeast, southwest: southwest))
XCTAssertEqual(
String(describing: state),
"CameraState.rect(northeast: CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4), southwest: CLLocationCoordinate2D(latitude: 34.5, longitude: 45.6))"
)
assertSnapshot(of: state, as: .description)
}
}
Original file line number Diff line number Diff line change
@@ -1,49 +1,37 @@
import CoreLocation
import SnapshotTesting
import XCTest
@testable import MapLibreSwiftUI

final class MapViewCameraTests: XCTestCase {
func testCenterCamera() {
let expectedCoordinate = CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4)
let pitch: CameraPitch = .freeWithinRange(minimum: 12, maximum: 34)
let direction: CLLocationDirection = 23

let camera = MapViewCamera.center(expectedCoordinate, zoom: 12, pitch: pitch, direction: direction)

XCTAssertEqual(camera.state, .centered(onCoordinate: expectedCoordinate))
XCTAssertEqual(camera.zoom, 12)
XCTAssertEqual(camera.pitch, pitch)
XCTAssertEqual(camera.direction, direction)
let camera = MapViewCamera.center(
CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4),
zoom: 5,
pitch: .freeWithinRange(minimum: 12, maximum: 34),
direction: 23
)

assertSnapshot(of: camera, as: .dump)
}

func testTrackingUserLocation() {
let pitch: CameraPitch = .freeWithinRange(minimum: 12, maximum: 34)
let camera = MapViewCamera.trackUserLocation(pitch: pitch)
let camera = MapViewCamera.trackUserLocation(zoom: 10, pitch: pitch)

XCTAssertEqual(camera.state, .trackingUserLocation)
XCTAssertEqual(camera.zoom, 10)
XCTAssertEqual(camera.pitch, pitch)
XCTAssertEqual(camera.direction, 0)
assertSnapshot(of: camera, as: .dump)
}

func testTrackUserLocationWithCourse() {
let pitch: CameraPitch = .freeWithinRange(minimum: 12, maximum: 34)
let camera = MapViewCamera.trackUserLocationWithCourse(zoom: 18, pitch: pitch)

XCTAssertEqual(camera.state, .trackingUserLocationWithCourse)
XCTAssertEqual(camera.zoom, 18)
XCTAssertEqual(camera.pitch, pitch)
XCTAssertEqual(camera.direction, 0)
assertSnapshot(of: camera, as: .dump)
}

func testTrackUserLocationWithHeading() {
let camera = MapViewCamera.trackUserLocationWithHeading()
let camera = MapViewCamera.trackUserLocationWithHeading(zoom: 10, pitch: .free)

XCTAssertEqual(camera.state, .trackingUserLocationWithHeading)
XCTAssertEqual(camera.zoom, 10)
XCTAssertEqual(camera.pitch, .free)
XCTAssertEqual(camera.direction, 0)
assertSnapshot(of: camera, as: .dump)
}

// TODO: Add additional camera tests once behaviors are added (e.g. rect)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CameraState.centered(onCoordinate: CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4))
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CameraState.rect(northeast: CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4), southwest: CLLocationCoordinate2D(latitude: 34.5, longitude: 45.6))
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CameraState.trackingUserLocation
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CameraState.trackingUserLocationWithCourse
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CameraState.trackingUserLocationWithHeading
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
▿ MapViewCamera
- direction: 23.0
- lastReasonForChange: Optional<CameraChangeReason>.none
▿ pitch: CameraPitch
▿ freeWithinRange: (2 elements)
- minimum: 12.0
- maximum: 34.0
▿ state: CameraState
▿ centered: (1 element)
▿ onCoordinate: CLLocationCoordinate2D
- latitude: 12.3
- longitude: 23.4
- zoom: 5.0
Original file line number Diff line number Diff line change
@@ -0,0 +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.CameraState.trackingUserLocationWithCourse
- zoom: 18.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
▿ MapViewCamera
- direction: 0.0
▿ lastReasonForChange: Optional<CameraChangeReason>
- some: CameraChangeReason.programmatic
- pitch: CameraPitch.free
- state: CameraState.CameraState.trackingUserLocationWithHeading
- zoom: 10.0
Original file line number Diff line number Diff line change
@@ -0,0 +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.CameraState.trackingUserLocation
- zoom: 10.0

0 comments on commit c22d05d

Please sign in to comment.