SwiftUI Map标注箭头随用户旋转地图保持道路朝向的实现方法
问题描述
现有代码里,标注箭头已经能和朝向340度的道路对齐,设备自身旋转时箭头也会同步旋转保持对齐,但用户手动旋转地图时,箭头没法继续保持和道路的对齐状态,需要修改代码实现这一功能。
修改方案
核心是要同时考虑设备朝向和地图相机的旋转角度,两者结合才能让箭头在用户旋转地图时依然对准道路方向。具体修改步骤如下:
- 新增状态变量监听地图相机的朝向变化,用户旋转地图时相机的
heading值会更新 - 调整箭头旋转角度的计算逻辑,把地图相机的朝向纳入计算,抵消地图旋转带来的视角偏移
- 优化角度计算的归一化处理,避免出现超出0-360度的异常旋转
修改后完整代码
import SwiftUI import CoreLocation import MapKit struct ContentView: View { let annotations = [ MapAnnotation(title: "Allen Rd N", coordinate: CLLocationCoordinate2D(latitude: 43.721484, longitude: -79.446093), course: 340) ] @State private var position: MapCameraPosition = .userLocation(followsHeading: true, fallback: .automatic) // 新增:存储当前地图相机的朝向角度 @State private var cameraHeading: Double = 0 @StateObject private var locationManager = LocationManager() var body: some View { VStack { Map(position: $position) { UserAnnotation() ForEach(annotations) { annotation in Annotation(annotation.title, coordinate: annotation.coordinate, anchor: .bottom) { // 传递设备朝向和相机朝向给标注视图 AnnotationView(annotation: annotation, deviceHeading: locationManager.trueNorthOffset, cameraHeading: cameraHeading) } .annotationTitles(.hidden) } } .mapStyle(.standard) .mapControls { MapUserLocationButton() MapCompass() } // 监听相机位置变化,实时更新相机朝向 .onChange(of: position) { newPosition in guard case .camera(let camera) = newPosition else { return } cameraHeading = camera.heading ?? 0 } } .padding() } } struct MapAnnotation: Identifiable { let id = UUID() let title: String let coordinate: CLLocationCoordinate2D let course: Double static func == (lhs: MapAnnotation, rhs: MapAnnotation) -> Bool { return lhs.id == rhs.id } } final class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate { @Published var trueNorthOffset: Double = 0 private let locationManager = CLLocationManager() override init() { super.init() locationManager.delegate = self checkLocationAuthorizationStatus() locationManager.desiredAccuracy = kCLLocationAccuracyBest locationManager.startUpdatingLocation() locationManager.startUpdatingHeading() } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { return } func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { return } func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) { self.trueNorthOffset = newHeading.trueHeading } func checkLocationAuthorizationStatus() { let authorizationStatus = locationManager.authorizationStatus switch authorizationStatus { case .notDetermined: locationManager.requestWhenInUseAuthorization() break case .restricted, .denied: break case .authorizedAlways, .authorizedWhenInUse: break @unknown default: break } } } struct AnnotationView: View { var annotation: MapAnnotation var deviceHeading: Double var cameraHeading: Double // 计算箭头最终旋转角度:道路朝向 - 设备朝向 + 相机朝向(抵消地图旋转) private var rotationAngle: Double { let angle = annotation.course - deviceHeading + cameraHeading // 确保角度在0-360度范围内,避免异常旋转 return angle.truncatingRemainder(dividingBy: 360) } var body: some View { Image(systemName: "arrow.up.circle.fill") .font(.system(size: 50, weight: .bold)) .foregroundStyle(Color.black) .rotationEffect(Angle(degrees: rotationAngle)) } }
关键逻辑说明
- 相机朝向监听:通过
onChange(of: position)捕捉地图相机的变化,提取相机的heading值,这个值代表地图当前相对于正北的旋转角度。 - 旋转角度计算:
- 原有逻辑
annotation.course - deviceHeading保证设备旋转时箭头对齐道路 - 新增
+ cameraHeading用来抵消用户旋转地图带来的视角偏移,让箭头始终指向道路的实际地理方向
- 原有逻辑
- 角度归一化:用
truncatingRemainder(dividingBy: 360)把角度限制在0-360度范围内,避免出现连续旋转的异常情况。
内容的提问来源于stack exchange,提问作者Rhythmus
相关产品推荐
相关产品推荐

