SwiftUI MapKit 如何在跟随用户时设置 pitch 和 distance?
MapCameraPosition.userLocation(followsHeading:fallback:) 这个公开接口无法在启用用户跟随时显式指定初始 pitch 和 distance。如果导航视角需要由应用控制,可以根据定位和方向数据持续更新 .camera(MapCamera(...)),自行实现跟随。
MapKit 可以显示指定的倾斜视角,但这个跟随接口只有 followsHeading 和 fallback 两个参数,详见 Apple 的接口文档。
为什么 fallback 和等待 5 秒都不能提供保证?
地图尚未确定用户位置时,fallback 用于指定备用相机位置。即使将它设为带有 pitch: 60 的 .camera(...),也不能保证定位成功后的跟随相机会继续使用这些参数。
先设置相机,再等待 5 秒切换模式,在当前环境中可能奏效,但有两个问题:
- 固定等待时间不能确认相机动画已经完成。
- 官方文档没有承诺
.userLocation会继承已有相机的pitch和distance。
“切换后恢复 0°”和“等待后保留倾斜角度”都只是当前环境下观察到的行为,不能直接推广到所有系统版本。即使准确等待动画完成,参数能否继承仍然没有文档保证。
如何显式控制倾斜视角并持续跟随?
可以把位置、方向和相机更新放在同一条控制流程中。收到有效的位置或方向更新时,重新构造 MapCamera,同时指定中心点、距离、朝向和俯仰角。
MapCamera 的初始化方法支持这四项参数。其中,distance 表示地图中心点到相机的距离,单位是米;heading 是相对真北的角度。
以下视图片段适用于 iOS 17 及以上版本,假定应用已经完成定位授权,并由上层定位服务在主线程更新 location 和 heading:
import SwiftUI
import MapKit
import CoreLocation
@available(iOS 17.0, *)
struct NavigationMap: View {
let location: CLLocation?
let heading: CLHeading?
@State private var position: MapCameraPosition = .automatic
@State private var lastHeading: CLLocationDirection = 0
var body: some View {
Map(position: $position, interactionModes: []) {
UserAnnotation()
}
.onChange(of: location, initial: true) { _, _ in
updateCamera()
}
.onChange(of: heading, initial: true) { _, _ in
updateCamera()
}
}
private func updateCamera() {
guard let location,
location.horizontalAccuracy >= 0 else {
return
}
if let heading,
heading.headingAccuracy >= 0,
heading.trueHeading >= 0 {
lastHeading = heading.trueHeading
} else if location.course >= 0 {
lastHeading = location.course
}
position = .camera(MapCamera(
centerCoordinate: location.coordinate,
distance: 800,
heading: lastHeading,
pitch: 60
))
}
}
示例每次更新都会明确请求 distance: 800 和 pitch: 60,无需切换到 .userLocation,也不依赖延时。地图手势暂时关闭,以便展示应用持续控制相机的行为。示例只覆盖相机更新,定位服务仍需由应用提供。
卡车导航应使用 heading 还是 course?
CLHeading 表示设备朝向,CLLocation.course 表示设备的移动方向。Apple 在方向与航向说明中区分了两者。
示例优先使用有效的 trueHeading,以接近原问题中跟随设备朝向的需求。如果需要地图始终沿车辆行驶方向旋转,应优先采用经过质量筛选的 course,并在停车或方向无效时保留最近的有效方向。不要每次都将无效的 course 替换为 0,否则地图可能突然转向正北。
接入现有应用时需要处理什么?
自行更新 .camera 后,应用还需要负责跟随逻辑。接入时应处理以下事项:
- 位置质量与平滑:示例只检查了位置是否有效。还应根据业务要求过滤过旧或精度不足的数据,并对位置和方向更新做平滑处理。
- 方向跨越 0°:从 359° 转到 1° 时,插值应采用最短旋转路径,避免地图绕一大圈。
- 手动浏览与恢复跟随:如果允许拖动或缩放,应明确设置跟随状态,暂停自动更新,并提供恢复跟随入口。
- 相机更新来源:跟随期间,避免其他逻辑同时把同一个
position改为.region、.automatic或.userLocation,造成视角跳变。
如果必须使用 .userLocation(followsHeading: true, ...) 的内置跟随模式,就需要接受该接口无法显式配置初始俯仰角和距离的限制。如果应用必须控制倾斜视角,可以自行更新相机。
备注:内容仅供参考。
