Skip to content

Commit

Permalink
Fix: recenter doesn't immediately take effect
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
michaelkirk committed Sep 27, 2024
1 parent d1f9ecc commit b5b55a7
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 31 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@ 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).

## Unreleased

### Added

- MLNMapViewCameraUpdating.setUserTrackingMode(_ mode: MLNUserTrackingMode, animated: Bool, completionHandler: (() -> Void)?)
Previously, you could only call `mapViewCameraUpdating.userTrackingMode = newMode`
without specifying `animated` or `completionHandler`.

### Fixed

- Fix broken animation when setting user tracking mode.
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.

## Version 0.1.0 - 2024-09-21

This project has migrated from Stadia Maps to the MapLibre organization!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
37 changes: 6 additions & 31 deletions Sources/MapLibreSwiftUI/MapViewCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,20 +128,11 @@ public class MapViewCoordinator<T: MapViewHostViewController>: NSObject, MLNMapV
mapView.minimumPitch = pitchRange.rangeValue.lowerBound
mapView.maximumPitch = pitchRange.rangeValue.upperBound
case let .trackingUserLocationWithHeading(zoom: zoom, pitch: pitch, pitchRange: pitchRange):
mapView.userTrackingMode = .followWithHeading
mapView.setUserTrackingMode(.followWithHeading, animated: animated) {
mapView.minimumPitch = pitchRange.rangeValue.lowerBound
mapView.maximumPitch = pitchRange.rangeValue.upperBound

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.setZoomLevel(zoom, animated: false)
mapView.minimumPitch = pitch
mapView.maximumPitch = pitch

} else {
let camera = mapView.camera

let altitude = MLNAltitudeForZoomLevel(
zoom,
pitch,
Expand All @@ -152,24 +143,11 @@ public class MapViewCoordinator<T: MapViewHostViewController>: NSObject, MLNMapV
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 {
// 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.setZoomLevel(zoom, animated: false)
mapView.minimumPitch = pitch
mapView.maximumPitch = pitch

} else {
mapView.setUserTrackingMode(.followWithCourse, animated: animated) {
mapView.minimumPitch = pitchRange.rangeValue.lowerBound
mapView.maximumPitch = pitchRange.rangeValue.upperBound
let camera = mapView.camera

let altitude = MLNAltitudeForZoomLevel(
zoom,
pitch,
Expand All @@ -180,9 +158,6 @@ public class MapViewCoordinator<T: MapViewHostViewController>: NSObject, MLNMapV
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,
Expand Down

0 comments on commit b5b55a7

Please sign in to comment.