返回

Swift Mapbox 地图 onCameraChanged 失效排查与解决

IOS

Swift 中 Mapbox 地图 onCameraChanged 事件失效问题排查与解决

在使用 Mapbox SDK 进行 iOS 地图应用开发时,监听地图相机变化 (onCameraChanged) 是常见的需求。通过此事件,可以实时获取地图视角的变化,例如平移、缩放等。一些开发者反映在 Swift 中使用 Mapbox SDK 时,onCameraChanged 事件无法按预期触发。本篇将探讨导致此问题的可能原因,并提供相应的解决方案。

一、问题分析

onCameraChanged 事件失效时,症状表现为在进行地图操作后,该事件对应的回调函数并未执行。究其原因,可能是如下因素:

  1. MapView 初始化配置不正确: Mapbox SDK 依赖正确的初始化配置。配置问题可能阻止 onCameraChanged 事件正确注册或触发。
  2. 事件监听方式不正确: Swift 使用 Combine 框架提供了一种新的事件处理机制,不正确的使用方式可能会导致无法成功监听事件。
  3. MapStyle 的加载: 特定的样式 URI 如果配置有问题, 或者加载存在延迟和错误, 都有可能会导致无法获取地图数据从而无法响应 onCameraChanged
  4. 代理冲突: 使用旧版本的代理方法和新的事件监听方式混合使用可能导致事件监听失效。
  5. 异步操作导致时机错乱: onCameraChanged 需要保证主线程执行回调。不恰当的线程调度,会错过事件。

二、解决方案及最佳实践

针对以上分析,提出以下解决方案,帮助开发者修复该问题并提高代码健壮性:

1. 精简mapInitOptions(),保持最基础设置。

Mapbox iOS SDK V10 之前主要依靠遵循 MGLMapViewDelegate 协议,并实现 -mapView:regionDidChangeAnimated:方法。
而在 iOS SDK V10之后,采用了新的Combine 框架的 onCameraChanged进行处理。如果项目中还使用了旧版本的API进行混合监听,则很有可能会产生不可预计的错误。因此我们需要简化并清理多余代理实现方法。
我们还需要确保 mapInitOptions() 提供必要的数据。对于简单的初始化,可以移除该回调。

操作步骤:

  1. 删除 ViewController 继承的 MapInitOptionsProvider 协议。
  2. 清理 mapInitOptions() 的实现。

2. 保证 MapboxMap 正确配置。

mapboxMap 对象的配置可能出错,导致地图在最基础层面上不可用。我们现在需要精简这部分代码:在 viewDidLoad() 内配置好 Mapbox 相关的必要内容。

操作步骤:

  1. 移除不必要的 UserDefaults 相关操作, 删除掉 let defaults = UserDefaults.standard
  2. 检查 ViewControllerself.mapView 的配置方式。 移除 .mapStyle = ,并清理 StyleURI

代码示例:

import UIKit
import MapboxMaps
import Combine

class ViewController: UIViewController, URLSessionDelegate {

    private var mapView: MapView!
    private var cancellables: Set<AnyCancelable> = []

    override func viewDidLoad() {
        super.viewDidLoad()

        let resourceOptions = ResourceOptions(accessToken: "YOUR_MAPBOX_ACCESS_TOKEN")
        let cameraOptions = CameraOptions(center: CLLocationCoordinate2D(latitude: 40.7128, longitude: -74.0060), zoom: 10)

        let mapInitOptions = MapInitOptions(resourceOptions: resourceOptions, cameraOptions: cameraOptions, styleURI: .streets)
        mapView = MapView(frame: view.bounds, mapInitOptions: mapInitOptions)
        
        view.addSubview(mapView)

        mapView.mapboxMap.onCameraChanged.observe { [weak self] cameraChanged in
            guard let self else { return }
            // 当 `onCameraChanged` 被触发, 控制台将打印log
            print("Camera changed: \(cameraChanged)")
            //updateMap() 在这调用。按需配置地图数据的更新操作
        }.store(in: &cancellables)

        mapView.gestures.onMapTap.observe { [unowned self] context in
            print("Map tapped at: \(context.point)")
        }.store(in: &cancellables)

        // ... 其他业务逻辑
    }

    // ... 其他业务逻辑代码
}

操作说明 : 将 YOUR_MAPBOX_ACCESS_TOKEN 替换为你的实际的access token,这个可以在Mapbox官网上获取到。替换经纬度为你所需要的地点信息。

3. 异步数据更新与主线程调度

有时候问题发生在:尽管监听到了地图的动作,但是你的处理操作涉及大量计算, 无法保证和事件流的匹配, 这也会使得事件监听表现的不如预期。需要更新处理地图数据方式。
推荐通过创建调度方法进行 onCameraChanged 回调。

操作步骤:

  1. 在 ViewController 中创建一个名为 handleCameraChange 的方法。

代码示例:

 func handleCameraChange(_ cameraChanged: CameraChanged) {
        // 将数据更新等大计算开销, 使用 DispatchQueue.global().async 执行
        DispatchQueue.global(qos: .userInitiated).async { [weak self] in
            guard let self = self else { return }
            
            // 这里放置更新数据,更新地图的相关代码...
            
            
            // 更新 UI,必须返回到主线程执行
            DispatchQueue.main.async {
                //更新UI的操作,更新标注物、更新文本等等。
                //print("Camera change handled, UI updated.")
            }
        }
    }

    override func viewDidLoad() {
        // ... 原有的代码

        mapView.mapboxMap.onCameraChanged.observe { [weak self] cameraChanged in
            guard let self else { return }
            self.handleCameraChange(cameraChanged)
        }.store(in: &cancellables)
      // ... 原有的代码
   }

操作说明 : 创建 handleCameraChange,该方法在地图变化的时候触发,在该方法内部使用合适的队列,去管理和分派不同优先级的任务。耗时且无需和UI发生关联的操作可以交给异步线程完成。只有涉及到更新UI的内容,再统一回到主线程完成,以此保证不会发生遗漏、或事件错乱等异常。

安全建议

  • 请务必在 Mapbox 官方网站获取 Access Token,并在项目中正确配置。切勿使用网络上的来源不明的密钥, 这样做会严重危害用户设备的安全。
  • 请勿直接通过硬编码暴露 Access Token。使用 Bundle ID 配置 Mapbox。保证地图访问合法合规,并符合 Mapbox 使用规定。
  • 使用最新版本的 Mapbox SDK,并参考官方最新文档以获取最新方法和性能改进,并且能规避许多旧版本已知的问题。
  • 处理用户定位和地图信息, 务必严格遵守隐私保护相关条款。请仔细查看关于 Mapbox 相关的权限和用户数据的文档。