Skip to content

Commit 899457f

Browse files
Fix: recenter doesn't immediately take effect (maplibre#53)
* Fix: recenter doesn't immediately take effect Note: I only ever reproduced the bug in the context my app, which uses this library via Ferrostar, while using the CoreLocationProvider, not the SimulatedLocationProvider. But I expect this to be an issue for anyone using this API. To reproduce the bug in my app: Setup: (all of this is working more or less as expected): - While stationary (not sure if this is required) - start a route - see the user location puck and map centered on my location on the ground. - pan the map off the route - see the user location puck disappear, but you still see the smaller location indicator on the map. (as expected) - see the "recenter me" button appear - tap the "recenter me" button Now here's the problem: At this point I'm expecting the map to recenter with the user location puck on my location on the ground. But instead, I see the user location puck centered at the *current* map position - where I'd previously panned to, *not* at my location on the ground. Interestingly, if you'd repeat the process at this point - panning and then re-tapping the "center me" button, it would *work* this second time. --- The new behavior is definitely an improvement. However, if you've zoomed way out while exploring the map away from your route, there is a slightly noticeable two-part action while we wait for the new completion block - first re-centering and then zooming. It's unquestionably better than the current behavior though. * Fixes from @hactar's branch * Disable swiftformat andOperator (see maplibre#54) * Fix inadvertent regression; update CHANGELOG --------- Co-authored-by: Ian Wagner <[email protected]>
1 parent 9b306fc commit 899457f

File tree

4 files changed

+80
-49
lines changed

4 files changed

+80
-49
lines changed

.swiftformat

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@
1212

1313
# rules
1414

15-
--enable isEmpty
15+
--enable isEmpty
16+
--disable andOperator

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## Version 0.2.0 - 2024-10-07
9+
10+
### Added
11+
12+
- `MLNMapViewCameraUpdating.setUserTrackingMode(_ mode: MLNUserTrackingMode, animated: Bool, completionHandler: (() -> Void)?)`
13+
in [#53](https://github.com/maplibre/swiftui-dsl/pull/53).
14+
Previously, you could only call `mapViewCameraUpdating.userTrackingMode = newMode`
15+
without specifying `animated` or `completionHandler`.
16+
17+
### Fixed
18+
19+
- Fix broken animation when setting user tracking mode in [#53](https://github.com/maplibre/swiftui-dsl/pull/53).
20+
For example, when tapping the "recenter" button in Ferrostar (which uses this
21+
package), the map now immediately re-centers on the users current location,
22+
whereas before you'd have to tap it twice. Note: the bug wasn't noticeable
23+
when using the Ferrostar's SimulatedLocationProvider.
24+
- Pitch range `.free` was being reset to `.freeWithinRange(0, 59.9999999)`
25+
Fixed in in [#54](https://github.com/maplibre/swiftui-dsl/pull/54).
26+
827
## Version 0.1.0 - 2024-09-21
928

1029
This project has migrated from Stadia Maps to the MapLibre organization!

Sources/MapLibreSwiftUI/Extensions/MapLibre/MLNMapViewCameraUpdating.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import Mockable
77
@Mockable
88
public protocol MLNMapViewCameraUpdating: AnyObject {
99
@MainActor var userTrackingMode: MLNUserTrackingMode { get set }
10+
@MainActor func setUserTrackingMode(_ mode: MLNUserTrackingMode, animated: Bool, completionHandler: (() -> Void)?)
11+
1012
@MainActor var minimumPitch: CGFloat { get set }
1113
@MainActor var maximumPitch: CGFloat { get set }
1214
@MainActor var direction: CLLocationDirection { get set }

Sources/MapLibreSwiftUI/MapViewCoordinator.swift

Lines changed: 57 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -98,91 +98,100 @@ public class MapViewCoordinator<T: MapViewHostViewController>: NSObject, MLNMapV
9898
mapView.minimumPitch = pitchRange.rangeValue.lowerBound
9999
mapView.maximumPitch = pitchRange.rangeValue.upperBound
100100
case let .trackingUserLocation(zoom: zoom, pitch: pitch, pitchRange: pitchRange, direction: direction):
101-
mapView.userTrackingMode = .follow
102-
103101
if mapView.frame.size == .zero {
104102
// On init, the mapView's frame is not set up yet, so manipulation via camera is broken,
105103
// so let's do something else instead.
106104
// Needs to be non-animated or else it messes up following
107105

106+
mapView.userTrackingMode = .follow
107+
108108
mapView.setZoomLevel(zoom, animated: false)
109109
mapView.direction = direction
110110

111111
mapView.minimumPitch = pitch
112112
mapView.maximumPitch = pitch
113+
mapView.minimumPitch = pitchRange.rangeValue.lowerBound
114+
mapView.maximumPitch = pitchRange.rangeValue.upperBound
113115

114116
} else {
115-
let camera = mapView.camera
116-
camera.heading = direction
117-
camera.pitch = pitch
118-
119-
let altitude = MLNAltitudeForZoomLevel(
120-
zoom,
121-
pitch,
122-
mapView.camera.centerCoordinate.latitude,
123-
mapView.frame.size
124-
)
125-
camera.altitude = altitude
126-
mapView.setCamera(camera, animated: animated)
117+
mapView.setUserTrackingMode(.follow, animated: animated) {
118+
mapView.minimumPitch = pitchRange.rangeValue.lowerBound
119+
mapView.maximumPitch = pitchRange.rangeValue.upperBound
120+
let camera = mapView.camera
121+
camera.heading = direction
122+
camera.pitch = pitch
123+
124+
let altitude = MLNAltitudeForZoomLevel(
125+
zoom,
126+
pitch,
127+
mapView.camera.centerCoordinate.latitude,
128+
mapView.frame.size
129+
)
130+
camera.altitude = altitude
131+
mapView.setCamera(camera, animated: animated)
132+
}
127133
}
128-
mapView.minimumPitch = pitchRange.rangeValue.lowerBound
129-
mapView.maximumPitch = pitchRange.rangeValue.upperBound
130134
case let .trackingUserLocationWithHeading(zoom: zoom, pitch: pitch, pitchRange: pitchRange):
131-
mapView.userTrackingMode = .followWithHeading
132-
133135
if mapView.frame.size == .zero {
134136
// On init, the mapView's frame is not set up yet, so manipulation via camera is broken,
135137
// so let's do something else instead.
136138
// Needs to be non-animated or else it messes up following
137139

140+
mapView.userTrackingMode = .followWithHeading
138141
mapView.setZoomLevel(zoom, animated: false)
139142
mapView.minimumPitch = pitch
140143
mapView.maximumPitch = pitch
144+
mapView.minimumPitch = pitchRange.rangeValue.lowerBound
145+
mapView.maximumPitch = pitchRange.rangeValue.upperBound
141146

142147
} else {
143-
let camera = mapView.camera
144-
145-
let altitude = MLNAltitudeForZoomLevel(
146-
zoom,
147-
pitch,
148-
mapView.camera.centerCoordinate.latitude,
149-
mapView.frame.size
150-
)
151-
camera.altitude = altitude
152-
camera.pitch = pitch
153-
mapView.setCamera(camera, animated: animated)
148+
mapView.setUserTrackingMode(.followWithHeading, animated: animated) {
149+
mapView.minimumPitch = pitchRange.rangeValue.lowerBound
150+
mapView.maximumPitch = pitchRange.rangeValue.upperBound
151+
let camera = mapView.camera
152+
153+
let altitude = MLNAltitudeForZoomLevel(
154+
zoom,
155+
pitch,
156+
mapView.camera.centerCoordinate.latitude,
157+
mapView.frame.size
158+
)
159+
camera.altitude = altitude
160+
camera.pitch = pitch
161+
mapView.setCamera(camera, animated: animated)
162+
}
154163
}
155-
156-
mapView.minimumPitch = pitchRange.rangeValue.lowerBound
157-
mapView.maximumPitch = pitchRange.rangeValue.upperBound
158164
case let .trackingUserLocationWithCourse(zoom: zoom, pitch: pitch, pitchRange: pitchRange):
159-
mapView.userTrackingMode = .followWithCourse
160-
161165
if mapView.frame.size == .zero {
166+
mapView.userTrackingMode = .followWithCourse
162167
// On init, the mapView's frame is not set up yet, so manipulation via camera is broken,
163168
// so let's do something else instead.
164169
// Needs to be non-animated or else it messes up following
165170

166171
mapView.setZoomLevel(zoom, animated: false)
167172
mapView.minimumPitch = pitch
168173
mapView.maximumPitch = pitch
174+
mapView.minimumPitch = pitchRange.rangeValue.lowerBound
175+
mapView.maximumPitch = pitchRange.rangeValue.upperBound
169176

170177
} else {
171-
let camera = mapView.camera
172-
173-
let altitude = MLNAltitudeForZoomLevel(
174-
zoom,
175-
pitch,
176-
mapView.camera.centerCoordinate.latitude,
177-
mapView.frame.size
178-
)
179-
camera.altitude = altitude
180-
camera.pitch = pitch
181-
mapView.setCamera(camera, animated: animated)
178+
mapView.setUserTrackingMode(.followWithCourse, animated: animated) {
179+
mapView.minimumPitch = pitchRange.rangeValue.lowerBound
180+
mapView.maximumPitch = pitchRange.rangeValue.upperBound
181+
182+
let camera = mapView.camera
183+
184+
let altitude = MLNAltitudeForZoomLevel(
185+
zoom,
186+
pitch,
187+
mapView.camera.centerCoordinate.latitude,
188+
mapView.frame.size
189+
)
190+
camera.altitude = altitude
191+
camera.pitch = pitch
192+
mapView.setCamera(camera, animated: animated)
193+
}
182194
}
183-
184-
mapView.minimumPitch = pitchRange.rangeValue.lowerBound
185-
mapView.maximumPitch = pitchRange.rangeValue.upperBound
186195
case let .rect(boundingBox, padding):
187196
mapView.setVisibleCoordinateBounds(boundingBox,
188197
edgePadding: padding,

0 commit comments

Comments
 (0)