diff --git a/.swiftformat b/.swiftformat index 8229b0f..dbddff6 100644 --- a/.swiftformat +++ b/.swiftformat @@ -12,4 +12,5 @@ # rules ---enable isEmpty \ No newline at end of file +--enable isEmpty +--disable andOperator \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 2729ab1..c1e14bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Version 0.2.0 - 2024-10-07 + +### Added + +- `MLNMapViewCameraUpdating.setUserTrackingMode(_ mode: MLNUserTrackingMode, animated: Bool, completionHandler: (() -> Void)?)` + in [#53](https://github.com/maplibre/swiftui-dsl/pull/53). + Previously, you could only call `mapViewCameraUpdating.userTrackingMode = newMode` + without specifying `animated` or `completionHandler`. + +### Fixed + +- Fix broken animation when setting user tracking mode in [#53](https://github.com/maplibre/swiftui-dsl/pull/53). + For example, when tapping the "recenter" button in Ferrostar (which uses this + package), the map now immediately re-centers on the users current location, + whereas before you'd have to tap it twice. Note: the bug wasn't noticeable + when using the Ferrostar's SimulatedLocationProvider. +- Pitch range `.free` was being reset to `.freeWithinRange(0, 59.9999999)` + Fixed in in [#54](https://github.com/maplibre/swiftui-dsl/pull/54). + ## Version 0.1.0 - 2024-09-21 This project has migrated from Stadia Maps to the MapLibre organization! diff --git a/Sources/MapLibreSwiftUI/Extensions/MapLibre/MLNMapViewCameraUpdating.swift b/Sources/MapLibreSwiftUI/Extensions/MapLibre/MLNMapViewCameraUpdating.swift index 2deaf4b..6f76657 100644 --- a/Sources/MapLibreSwiftUI/Extensions/MapLibre/MLNMapViewCameraUpdating.swift +++ b/Sources/MapLibreSwiftUI/Extensions/MapLibre/MLNMapViewCameraUpdating.swift @@ -7,6 +7,8 @@ import Mockable @Mockable public protocol MLNMapViewCameraUpdating: AnyObject { @MainActor var userTrackingMode: MLNUserTrackingMode { get set } + @MainActor func setUserTrackingMode(_ mode: MLNUserTrackingMode, animated: Bool, completionHandler: (() -> Void)?) + @MainActor var minimumPitch: CGFloat { get set } @MainActor var maximumPitch: CGFloat { get set } @MainActor var direction: CLLocationDirection { get set } diff --git a/Sources/MapLibreSwiftUI/MapViewCoordinator.swift b/Sources/MapLibreSwiftUI/MapViewCoordinator.swift index ea87875..ee5c93c 100644 --- a/Sources/MapLibreSwiftUI/MapViewCoordinator.swift +++ b/Sources/MapLibreSwiftUI/MapViewCoordinator.swift @@ -98,67 +98,72 @@ public class MapViewCoordinator: NSObject, MLNMapV mapView.minimumPitch = pitchRange.rangeValue.lowerBound mapView.maximumPitch = pitchRange.rangeValue.upperBound case let .trackingUserLocation(zoom: zoom, pitch: pitch, pitchRange: pitchRange, direction: direction): - mapView.userTrackingMode = .follow - 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.userTrackingMode = .follow + mapView.setZoomLevel(zoom, animated: false) mapView.direction = direction mapView.minimumPitch = pitch mapView.maximumPitch = pitch + mapView.minimumPitch = pitchRange.rangeValue.lowerBound + mapView.maximumPitch = pitchRange.rangeValue.upperBound } 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.setUserTrackingMode(.follow, animated: animated) { + mapView.minimumPitch = pitchRange.rangeValue.lowerBound + mapView.maximumPitch = pitchRange.rangeValue.upperBound + 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 - 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.userTrackingMode = .followWithHeading mapView.setZoomLevel(zoom, animated: false) mapView.minimumPitch = pitch mapView.maximumPitch = pitch + mapView.minimumPitch = pitchRange.rangeValue.lowerBound + mapView.maximumPitch = pitchRange.rangeValue.upperBound } 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.setUserTrackingMode(.followWithHeading, animated: animated) { + mapView.minimumPitch = pitchRange.rangeValue.lowerBound + mapView.maximumPitch = pitchRange.rangeValue.upperBound + 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 - if mapView.frame.size == .zero { + mapView.userTrackingMode = .followWithCourse // 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 @@ -166,23 +171,27 @@ public class MapViewCoordinator: NSObject, MLNMapV mapView.setZoomLevel(zoom, animated: false) mapView.minimumPitch = pitch mapView.maximumPitch = pitch + mapView.minimumPitch = pitchRange.rangeValue.lowerBound + mapView.maximumPitch = pitchRange.rangeValue.upperBound } 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.setUserTrackingMode(.followWithCourse, animated: animated) { + mapView.minimumPitch = pitchRange.rangeValue.lowerBound + mapView.maximumPitch = pitchRange.rangeValue.upperBound + + 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,