Skip to content

Commit 282feca

Browse files
authored
Stop navigation without dismissing UI (#3880)
* vk-1228-stop-navigation: added Router.finishRouting method to explicitly end navigation session and updates, without dismissing related UI and components; added Unit test; CHANGELOG updated
1 parent 34e7fe3 commit 282feca

File tree

5 files changed

+102
-4
lines changed

5 files changed

+102
-4
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changes to the Mapbox Navigation SDK for iOS
22

3+
## main
4+
5+
### Routing
6+
7+
* Added `Router.finishRouting()` method to finish routing session without dismissing related UI and logic components.([#3880](https://github.com/mapbox/mapbox-navigation-ios/pull/3880))
8+
39
## v2.5.0
410

511
### Packaging

Sources/MapboxCoreNavigation/LegacyRouteController.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ open class LegacyRouteController: NSObject, Router, InternalRouter, CLLocationMa
141141
public func updateRoute(with indexedRouteResponse: IndexedRouteResponse,
142142
routeOptions: RouteOptions?,
143143
completion: ((Bool) -> Void)?) {
144+
guard !hasFinishedRouting else { return }
144145
updateRoute(with: indexedRouteResponse, routeOptions: routeOptions, isProactive: false, completion: completion)
145146
}
146147

@@ -160,6 +161,7 @@ open class LegacyRouteController: NSObject, Router, InternalRouter, CLLocationMa
160161

161162

162163
public func advanceLegIndex(completionHandler: AdvanceLegCompletionHandler? = nil) {
164+
guard !hasFinishedRouting else { return }
163165
precondition(!routeProgress.isFinalLeg, "Can not increment leg index beyond final leg.")
164166
routeProgress.legIndex += 1
165167
BillingHandler.shared.beginNewBillingSessionIfRunning(with: sessionUUID)
@@ -347,13 +349,21 @@ open class LegacyRouteController: NSObject, Router, InternalRouter, CLLocationMa
347349
userSnapToStepDistanceFromManeuver = shape.distance(from: coordinate)
348350
}
349351

352+
private var hasFinishedRouting = false
353+
public func finishRouting() {
354+
hasFinishedRouting = true
355+
BillingHandler.shared.stopBillingSession(with: sessionUUID)
356+
}
357+
350358
// MARK: Handling LocationManager Output
351359

352360
public func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
361+
guard !hasFinishedRouting else { return }
353362
heading = newHeading
354363
}
355364

356365
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
366+
guard !hasFinishedRouting else { return }
357367
let filteredLocations = locations.filter {
358368
return $0.isQualified
359369
}
@@ -457,6 +467,7 @@ open class LegacyRouteController: NSObject, Router, InternalRouter, CLLocationMa
457467
}
458468

459469
public func reroute(from location: CLLocation, along progress: RouteProgress) {
470+
guard !hasFinishedRouting else { return }
460471
if let lastRerouteLocation = lastRerouteLocation {
461472
guard location.distance(from: lastRerouteLocation) >= RouteControllerMaximumDistanceBeforeRecalculating else {
462473
return

Sources/MapboxCoreNavigation/RouteController.swift

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ open class RouteController: NSObject {
158158
`RouteLeg` was changed or not.
159159
*/
160160
public func advanceLegIndex(completionHandler: AdvanceLegCompletionHandler? = nil) {
161+
guard !hasFinishedRouting else { return }
161162
updateRouteLeg(to: routeProgress.legIndex + 1) { result in
162163
completionHandler?(result)
163164
}
@@ -235,6 +236,7 @@ open class RouteController: NSObject {
235236
var previousArrivalWaypoint: MapboxDirections.Waypoint?
236237

237238
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
239+
guard !hasFinishedRouting else { return }
238240
guard let location = locations.last else { return }
239241

240242
guard !(delegate?.router(self, shouldDiscard: location) ?? DefaultBehavior.shouldDiscardLocation) else {
@@ -251,6 +253,7 @@ open class RouteController: NSObject {
251253
}
252254

253255
public func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
256+
guard !hasFinishedRouting else { return }
254257
heading = newHeading
255258
}
256259

@@ -332,6 +335,16 @@ open class RouteController: NSObject {
332335
update(to: status)
333336
}
334337

338+
private var hasFinishedRouting = false
339+
public func finishRouting() {
340+
guard !hasFinishedRouting else { return }
341+
hasFinishedRouting = true
342+
removeRoutes(completion: nil)
343+
BillingHandler.shared.stopBillingSession(with: sessionUUID)
344+
unsubscribeNotifications()
345+
routeTask?.cancel()
346+
}
347+
335348
private func update(to status: NavigationStatus) {
336349
guard let rawLocation = rawLocation,
337350
isValidNavigationStatus(status)
@@ -623,10 +636,7 @@ open class RouteController: NSObject {
623636
}
624637

625638
deinit {
626-
removeRoutes(completion: nil)
627-
BillingHandler.shared.stopBillingSession(with: sessionUUID)
628-
unsubscribeNotifications()
629-
routeTask?.cancel()
639+
finishRouting()
630640
rerouteController.resetToDefaultSettings()
631641
Self.instanceLock.lock()
632642
Self.instance = nil
@@ -729,6 +739,7 @@ extension RouteController: Router {
729739
}
730740

731741
public func reroute(from location: CLLocation, along progress: RouteProgress) {
742+
guard !hasFinishedRouting else { return }
732743
guard customRoutingProvider != nil else {
733744
rerouteController.forceReroute()
734745
return
@@ -765,6 +776,7 @@ extension RouteController: Router {
765776
public func updateRoute(with indexedRouteResponse: IndexedRouteResponse,
766777
routeOptions: RouteOptions?,
767778
completion: ((Bool) -> Void)?) {
779+
guard !hasFinishedRouting else { return }
768780
updateRoute(with: indexedRouteResponse, routeOptions: routeOptions, isProactive: false, completion: completion)
769781
}
770782

Sources/MapboxCoreNavigation/Router.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,12 @@ public protocol Router: CLLocationManagerDelegate {
199199
func updateRoute(with indexedRouteResponse: IndexedRouteResponse,
200200
routeOptions: RouteOptions?,
201201
completion: ((Bool) -> Void)?)
202+
203+
/// Forcefully stop navigation process without ability to continue it.
204+
///
205+
/// Use this method to indicate that you no longer need navigation experience for current session/UI.
206+
/// After finishing, `Router` will not be able to update route, route leg, issue a reroute or do any other update, related to route traversing.
207+
func finishRouting()
202208
}
203209

204210
protocol InternalRouter: AnyObject {

Tests/MapboxCoreNavigationTests/MapboxCoreNavigationTests.swift

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,4 +482,67 @@ class MapboxCoreNavigationTests: TestCase {
482482
XCTAssertNil(error)
483483
}
484484
}
485+
486+
func testNoUpdatesAfterFinishingRouting() {
487+
navigation = MapboxNavigationService(routeResponse: response,
488+
routeIndex: 0,
489+
routeOptions: routeOptions,
490+
customRoutingProvider: MapboxRoutingProvider(.offline),
491+
credentials: Fixture.credentials,
492+
simulating: .never)
493+
494+
// Coordinates from first step
495+
let coordinates = route.legs[0].steps[0].shape!.coordinates
496+
let now = Date()
497+
let locations = coordinates.enumerated().map {
498+
CLLocation(coordinate: $0.element,
499+
altitude: -1,
500+
horizontalAccuracy: 10,
501+
verticalAccuracy: -1,
502+
course: -1,
503+
speed: 10,
504+
timestamp: now + $0.offset)
505+
}
506+
507+
var hasFinishedRouting = false
508+
expectation(forNotification: .routeControllerProgressDidChange, object: navigation.router) { _ in
509+
return hasFinishedRouting
510+
}.isInverted = true
511+
expectation(forNotification: .routeControllerWillReroute, object: navigation.router) { _ in
512+
return hasFinishedRouting
513+
}.isInverted = true
514+
expectation(forNotification: .routeControllerDidReroute, object: navigation.router) { _ in
515+
return hasFinishedRouting
516+
}.isInverted = true
517+
518+
let routerDelegateSpy = RouterDelegateSpy()
519+
navigation.router.delegate = routerDelegateSpy
520+
521+
routerDelegateSpy.onShouldDiscard = { _ in
522+
if hasFinishedRouting {
523+
XCTFail("Location updates should not be tracked.")
524+
}
525+
return false
526+
}
527+
528+
navigation.start()
529+
530+
for location in locations {
531+
navigation.locationManager(navigation.locationManager, didUpdateLocations: [location])
532+
533+
if !hasFinishedRouting {
534+
navigation.router.finishRouting()
535+
hasFinishedRouting = true
536+
537+
navigation.updateRoute(with: IndexedRouteResponse(routeResponse: response,
538+
routeIndex: 0),
539+
routeOptions: nil,
540+
completion: nil)
541+
}
542+
}
543+
544+
waitForExpectations(timeout: waitForInterval) { (error) in
545+
XCTAssertNil(error)
546+
}
547+
}
485548
}

0 commit comments

Comments
 (0)