You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS端Mapbox导航语音重复问题排查求助

Mapbox iOS逐向导航语音重复叠加问题

在iOS端使用Mapbox逐向导航时,需在导航激活状态下添加途经点。通过addSpots方法为地图添加点图层后,点显示正常,但出现以下问题:

  • 关闭导航后重新打开,语音播报会重复叠加,关闭再打开的次数越多,语音重复层数越多
  • 移除addSpots方法后,语音重复问题消失

已做排查与尝试

  • 推测控制器关闭时导航未正确终止,新会话与旧会话发生重叠
  • 尝试在导航控制器dismiss时调用navigationServices.stop(),但无效果

相关代码

import Foundation
import UIKit
import XamoomSDK
import MBProgressHUD
import FirebaseAuth
import NotificationCenter
import MapboxDirections
import MapboxCoreNavigation
import MapboxNavigation
import MapboxMaps
import CoreLocation
import MBProgressHUD

class BasicViewController: UIViewController {
    
    var wayPoint: [Waypoint] = []
    var spots: [XMMSpot] = []
    var customImageMarker: UIImage?
    var layer: [String] = []
    
    convenience init(waypoints: [Waypoint], spots: [XMMSpot], customImageMarker: UIImage, layer: [String]) {
        self.init(nibName: nil, bundle: nil)
        self.wayPoint = waypoints
        self.spots = spots
        self.customImageMarker = customImageMarker
        self.layer = layer
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        requestRoute()
    }
    
    func requestRoute() {
        let navigationRoutOptions = NavigationRouteOptions(waypoints: wayPoint)
        
        Directions.shared.calculate(navigationRoutOptions) { [weak self] (_, result) in
            switch result {
            case .failure(let error):
                print(error.localizedDescription)
            case .success(let response):
                guard let strongSelf = self else {
                    return
                }
                
                let navigationService = MapboxNavigationService(routeResponse: response, routeIndex: 0, routeOptions: navigationRoutOptions, credentials: NavigationSettings.shared.directions.credentials, simulating: .none)
                
                let navigationOptions = NavigationOptions(navigationService: navigationService)
                let navigationViewController = NavigationViewController(for: response, routeIndex: 0, routeOptions: navigationRoutOptions,
                                                                        navigationOptions: navigationOptions)
                
                navigationViewController.delegate = strongSelf
                
                self?.addSpots(navViewController: (navigationViewController))
                
                navigationViewController.modalPresentationStyle = .fullScreen
                navigationViewController.routeLineTracksTraversal = true
                
                strongSelf.present(navigationViewController, animated: true, completion: nil)
            }
        }
    }
    
    func addSpots(navViewController: NavigationViewController) {
        navViewController.navigationMapView?.mapView.mapboxMap.onEvery(event: .styleLoaded) {  _ in
          for spot in self.spots {
              let style = navViewController.navigationMapView?.mapView.mapboxMap.style
            try? style?.addImage((self.customImageMarker!), id: "markerimage", stretchX: [], stretchY: [])

            let someCoordinate = CLLocationCoordinate2DMake(spot.latitude, spot.longitude)
            let point = Point(someCoordinate)

            var feature = Feature(geometry: Geometry.point(point))
            feature.identifier = .string(spot.name)

            var source = GeoJSONSource()
            source.data = .feature(feature)
            try? navViewController.navigationMapView?.mapView.mapboxMap.style.addSource(source, id: spot.name)
            self.layer.append(spot.name)

            var layer = SymbolLayer(id: spot.name)
            layer.source = spot.name
            layer.iconImage = .constant(.name("markerimage"))
            layer.iconAnchor = .constant(.bottom)
            layer.textField = .constant(spot.name)
            layer.iconAllowOverlap = .constant(true)
            try? navViewController.navigationMapView?.mapView.mapboxMap.style.addLayer(layer)
          }
        }
    }
}

extension BasicViewController: NavigationViewControllerDelegate {
    func navigationViewControllerDidDismiss(_ navigationViewController: NavigationViewController, byCanceling canceled: Bool) {
        dismiss(animated: true, completion: nil)
    }
}

下一步排查方案

  1. 清理重复的事件监听
    addSpots中使用onEvery(event: .styleLoaded)会重复注册事件监听,即使旧导航关闭,监听仍可能绑定在地图实例上。需保存监听token并在导航关闭时移除:

    // 新增属性保存监听token
    var styleLoadedToken: Cancelable?
    
    func addSpots(navViewController: NavigationViewController) {
        styleLoadedToken = navViewController.navigationMapView?.mapView.mapboxMap.onEvery(event: .styleLoaded) { [weak self] _ in
            // 原有图层添加逻辑
        }
    }
    
    // 在导航关闭时移除监听
    func navigationViewControllerDidDismiss(_ navigationViewController: NavigationViewController, byCanceling canceled: Bool) {
        styleLoadedToken?.cancel()
        dismiss(animated: true, completion: nil)
    }
    
  2. 导航关闭时彻底清理图层资源
    每次导航关闭时,移除之前添加的所有source、layer和图片资源,避免旧资源残留:

    func navigationViewControllerDidDismiss(_ navigationViewController: NavigationViewController, byCanceling canceled: Bool) {
        guard let style = navigationViewController.navigationMapView?.mapView.mapboxMap.style else {
            dismiss(animated: true, completion: nil)
            return
        }
        // 移除所有自定义图层和数据源
        for layerId in self.layer {
            style.removeLayer(withId: layerId)
            style.removeSource(withId: layerId)
        }
        self.layer.removeAll()
        // 移除自定义图片
        style.removeImage(withId: "markerimage")
        // 停止并销毁导航服务
        navigationViewController.navigationService.shutdown()
        dismiss(animated: true, completion: nil)
    }
    
  3. 修正导航服务的生命周期管理
    将navigationService声明为类属性,确保在关闭时能准确终止并释放:

    class BasicViewController: UIViewController {
        var navigationService: MapboxNavigationService?
        // ...其他属性
        
        func requestRoute() {
            // ...原有逻辑
            self.navigationService = MapboxNavigationService(routeResponse: response, routeIndex: 0, routeOptions: navigationRoutOptions, credentials: NavigationSettings.shared.directions.credentials, simulating: .none)
            let navigationOptions = NavigationOptions(navigationService: self.navigationService!)
            // ...后续逻辑
        }
        
        func navigationViewControllerDidDismiss(_ navigationViewController: NavigationViewController, byCanceling canceled: Bool) {
            navigationService?.shutdown()
            navigationService = nil
            dismiss(animated: true, completion: nil)
        }
    }
    
  4. 避免重复添加图片资源
    addSpots中每次循环都调用addImage会重复添加同名图片,改为仅添加一次:

    func addSpots(navViewController: NavigationViewController) {
        navViewController.navigationMapView?.mapView.mapboxMap.onEvery(event: .styleLoaded) { [weak self] _ in
            guard let self = self, let style = navViewController.navigationMapView?.mapView.mapboxMap.style else { return }
            // 检查图片是否已存在,不存在再添加
            if style.image(withId: "markerimage") == nil {
                try? style.addImage(self.customImageMarker!, id: "markerimage", stretchX: [], stretchY: [])
            }
            // 原有添加source和layer的逻辑
        }
    }
    

内容的提问来源于stack exchange,提问作者Ivan Magda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 20:15:41