mapbox / mapbox-navigation-ios

Turn-by-turn navigation logic and UI in Swift on iOS
https://docs.mapbox.com/ios/navigation/
Other
863 stars 314 forks source link

[Bug]: Cannot assign to property: 'route' is a get-only property to NavigationViewController #3991

Closed nastasiupta closed 2 years ago

nastasiupta commented 2 years ago

Mapbox Navigation SDK version

2.5.1

Steps to reproduce

  1. Trigger rerouting function func navigationViewController(_ navigationViewController: NavigationViewController, shouldRerouteFrom location: CLLocation) -> Bool {

  2. Generate a new Route object, using your custom algorithm to (locations from current user location to destination)

  3. Assign the new Route object to NavigationViewController navigationViewController.navigationService.route = route Cannot assign to property: 'route' is a get-only property On older SDK versions it worked just to assign the new Route object to existing NavigationViewController NavigationService, but with the new SDK version is not working anymore. How can this behavior be achieved again?

Expected behavior

Just assign the new Route object to NavigationViewController and the SDK is doing it's job. It worked like that before.

Actual behavior

I get this error right now: Cannot assign to property: 'route' is a get-only property

Is this a one-time issue or a repeatable issue?

repeatable

MaximAlien commented 2 years ago

/cc @Udumft

Udumft commented 2 years ago

Hi! route is now a readonly property due to updates to the use an indexed route response as a source of routing information. Route can now be updated by a method call on a router or navigation service as shown here. Also, if you are implementing your own reroute generation method, consider implementing it as a custom RoutingProvider as a designed way to do so. You can see this short example on how to do it.

nastasiupta commented 2 years ago

@Udumft As I saw in the exemple you don't use func navigationViewController(_ navigationViewController: NavigationViewController, shouldRerouteFrom location: CLLocation) -> Bool { method. Also, you don't set the delegate for NavigationViewController. I noticed that the functions from the custom object are not called. Is this because I use the delegate? In my case I still need the delegate method to update the custom bottom view, using this method func navigationViewController(_ navigationViewController: NavigationViewController, didUpdate progress: RouteProgress, with location: CLLocation, rawLocation: CLLocation) {

Maybe snapshot for using navigation view controller will help to understand my setup: `

private func startNavigationWith(coordinates: [CLLocationCoordinate2D], completion: (() -> Void)?) {
    let matchOptions = NavigationMatchOptions(coordinates: coordinates, profileIdentifier: .automobile)
    matchOptions.includesSteps = true
    matchOptions.waypointIndices = IndexSet([0, coordinates.count - 1])
    matchOptions.distanceMeasurementSystem = .imperial

    Directions.shared.calculate(matchOptions) { [weak self] (_, result) in
        switch result {
        case .failure(let error):
            LoadingIndicator.hide {
                UIAlertController.show(title: .serverError, message: .custom(error.localizedDescription))
            }
        case .success(let matchResponse):
            guard let strongSelf = self else {
                return
            }
            do {
                let routingResponse = try RouteResponse(matching: matchResponse, options: matchOptions, credentials: NavigationSettings.shared.directions.credentials)
                let routeOptions = NavigationRouteOptions(navigationMatchOptions: matchOptions)
                let responseOptions = ResponseOptions.route(routeOptions)
                // This is to replace responseOptions from match to route
                let routingResponse2 = RouteResponse(httpResponse: routingResponse.httpResponse, routes: routingResponse.routes, waypoints: routingResponse.waypoints, options: responseOptions, credentials: NavigationSettings.shared.directions.credentials)
                guard let route = routingResponse2.routes?.first, let _ = route.legs.first else {
                    LoadingIndicator.hide(animated: false) {
                        UIAlertController.show(title: .serverError, message: .custom("Can't start route"))
                    }
                    return
                }
                let navigationService = MapboxNavigationService(routeResponse: routingResponse2,
                                                                routeIndex: 0,
                                                                routeOptions: routeOptions,
                                                                customRoutingProvider: strongSelf.customRoutingProvider,
                                                                credentials: NavigationSettings.shared.directions.credentials,
                                                                simulating: .onPoorGPS)
                let containerViewController = UIViewController.instantiate(type: .navigationBottomView) as? ContainerViewController
                let instructionsCardCollection = InstructionsCardViewController()
                let navigationOptions = NavigationOptions(styles: [MotourNavigationDayStyle(), MotourNavigationNightStyle()], navigationService: navigationService, topBanner: instructionsCardCollection, bottomBanner: containerViewController)
                let navigationViewController = NavigationViewController(navigationService: navigationService)
                strongSelf.navigationViewController = navigationViewController
                navigationViewController.navigationOptions = navigationOptions
                navigationViewController.showsReportFeedback = false
                navigationViewController.modalPresentationStyle = .fullScreen
                navigationViewController.showsSpeedLimits = true
                //                        navigationViewController.navigationView.speedLimitView
                navigationViewController.delegate = strongSelf
                navigationViewController.navigationMapView?.delegate = strongSelf
                navigationViewController.showsEndOfRouteFeedback = false
                if let navigationBottomView = containerViewController as? NavigationBottomView {
                    strongSelf.navigationBottomView = navigationBottomView
                    navigationBottomView.setup(progress: navigationViewController.navigationService.routeProgress)
                    navigationBottomView.setup(state: strongSelf.state)
                    navigationBottomView.delegate = strongSelf
                }
                let indexedRouteResponse = IndexedRouteResponse(routeResponse: routingResponse, routeIndex: 0)
                navigationViewController.navigationService.router.updateRoute(with: indexedRouteResponse, routeOptions: routeOptions, completion: nil)
                LoadingIndicator.hide(animated: false) {
                    strongSelf.parentViewController?.present(navigationViewController, animated: true)
                    completion?()
                }
                strongSelf.setupProgressUpdates()
            } catch {
                LoadingIndicator.hide {
                    UIAlertController.show(title: .serverError, message: .serverError)
                }
            }
        }
    }
}

`

Udumft commented 2 years ago

Linked example illustrates a different approach in case you are looking for overriding a reroute calculation. If you are not - using updateRoute will do what you are trying to achieve by attempting to update route property.

nastasiupta commented 2 years ago

@Udumft

func navigationViewController(_ navigationViewController: NavigationViewController, shouldRerouteFrom location: CLLocation) -> Bool {
    guard isRerouting == false,
          let rideType = rideType,
          let navigatedRouteIndex = navigatedRouteIndex,
          navigatedRouteIndex < rideType.schedule.routes.count else { return false }
    isRerouting = true
    func handle(locations: [MLocation]) {
        mapBoxRouteEntityConvertorWorker.execute(data: locations) { [weak self] _, routeResponse in
            guard let strongSelf = self else { return }
            strongSelf.isRerouting = false
            if let routeResponse = routeResponse {
                navigationViewController.navigationService.router.updateRoute(with: IndexedRouteResponse(routeResponse: routeResponse, routeIndex: 0), routeOptions: nil, completion: nil)
            }
        }
    }

    let route = rideType.schedule.routes[navigatedRouteIndex]
    rawRouteDetectorWorker.execute(data: .init(start: MLocation(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude), destination: route.destinationPlace.location, options: route.options, wayType: route.wayType)) { [weak self] error, locations in
        guard let strongSelf = self else { return }
        if let locations = locations, locations.count > 0 {
            handle(locations: locations)
        } else {
            strongSelf.rawRouteDetectorWorker.execute(data: .init(start: MLocation(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude), destination: route.destinationPlace.location, options: [], wayType: .fastest)) { [weak self] error, locations in
                guard let strongSelf = self else { return }
                if let locations = locations, locations.count > 0 {
                    handle(locations: locations)
                } else {
                    strongSelf.isRerouting = false
                }
            }
        }
    }
    return false
}

This should solve the issue? returning false in this function and using the navigationViewController.navigationService.router.updateRoute function?

Udumft commented 2 years ago

It looks like you are trying to provide your own reroute instead of a default calculated one. Your snippet will get it done, but you can also achieve it by following the example. Providing your route in this method will result in SDK treating this route as usual reroute, continuing the usual rerouting pipeline. Just make sure to set your custom RoutingProvider to related navigation service.