mapkit-location
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMapKit and CoreLocation
MapKit和CoreLocation
Build map-based and location-aware features targeting iOS 17+ with SwiftUI
MapKit and modern CoreLocation async APIs. Use with
for views, for streaming location, and
for geofencing.
MapMapContentBuilderCLLocationUpdate.liveUpdates()CLMonitorSee for extended MapKit patterns and
for CoreLocation patterns.
references/mapkit-patterns.mdreferences/corelocation-patterns.md面向iOS 17+版本,结合SwiftUI、MapKit与现代CoreLocation异步API构建基于地图和具备位置感知能力的功能。使用带有的组件构建视图,通过获取流式位置数据,使用实现地理围栏功能。
MapContentBuilderMapCLLocationUpdate.liveUpdates()CLMonitor查看获取更多MapKit使用模式,查看获取CoreLocation使用模式。
references/mapkit-patterns.mdreferences/corelocation-patterns.mdWorkflow
工作流
1. Add a map with markers or annotations
1. 添加带标记或标注的地图
- Import .
MapKit - Create a view with optional
Mapbinding.MapCameraPosition - Add ,
Marker,Annotation,MapPolyline, orMapPolygoninside theMapCircleclosure.MapContentBuilder - Configure map style with .
.mapStyle() - Add map controls with .
.mapControls { } - Handle selection with a binding.
selection:
- 导入。
MapKit - 创建带有可选绑定的
MapCameraPosition视图。Map - 在闭包中添加
MapContentBuilder、Marker、Annotation、MapPolyline或MapPolygon。MapCircle - 使用配置地图样式。
.mapStyle() - 通过添加地图控件。
.mapControls { } - 使用绑定处理选中事件。
selection:
2. Track user location
2. 追踪用户位置
- Add to Info.plist.
NSLocationWhenInUseUsageDescription - On iOS 18+, create a to manage authorization.
CLServiceSession - Iterate in a
CLLocationUpdate.liveUpdates().Task - Filter updates by distance or accuracy before updating the UI.
- Stop the task when location tracking is no longer needed.
- 在Info.plist中添加配置。
NSLocationWhenInUseUsageDescription - 在iOS 18+系统上,创建管理权限授权。
CLServiceSession - 在中迭代
Task获取位置更新。CLLocationUpdate.liveUpdates() - 更新UI前按距离或精度过滤位置更新。
- 不再需要位置追踪时停止任务。
3. Search for places
3. 搜索地点
- Configure for autocomplete suggestions.
MKLocalSearchCompleter - Debounce user input (at least 300ms) before setting the query.
- Convert selected completion to for full results.
MKLocalSearch.Request - Display results as markers or in a list.
- 配置实现自动补全建议。
MKLocalSearchCompleter - 设置查询前对用户输入做防抖处理(至少300ms)。
- 将选中的补全结果转换为获取完整搜索结果。
MKLocalSearch.Request - 将结果以标记或列表形式展示。
4. Get directions and display a route
4. 获取路线并展示
- Create an with source and destination
MKDirections.Request.MKMapItem - Set (
transportType,.automobile,.walking,.transit)..cycling - Await .
MKDirections.calculate() - Draw the route with .
MapPolyline(route.polyline)
- 使用起点和终点创建
MKMapItem。MKDirections.Request - 设置(
transportType驾车、.automobile步行、.walking公共交通、.transit骑行)。.cycling - 等待返回结果。
MKDirections.calculate() - 使用绘制路线。
MapPolyline(route.polyline)
5. Review existing map/location code
5. 审查现有地图/定位代码
Run through the Review Checklist at the end of this file.
按照本文件末尾的审查清单逐一检查。
SwiftUI Map View (iOS 17+)
SwiftUI Map视图(iOS 17+)
swift
import MapKit
import SwiftUI
struct PlaceMap: View {
@State private var position: MapCameraPosition = .automatic
var body: some View {
Map(position: $position) {
Marker("Apple Park", coordinate: applePark)
Marker("Infinite Loop", systemImage: "building.2",
coordinate: infiniteLoop)
}
.mapStyle(.standard(elevation: .realistic))
.mapControls {
MapUserLocationButton()
MapCompass()
MapScaleView()
}
}
}swift
import MapKit
import SwiftUI
struct PlaceMap: View {
@State private var position: MapCameraPosition = .automatic
var body: some View {
Map(position: $position) {
Marker("Apple Park", coordinate: applePark)
Marker("Infinite Loop", systemImage: "building.2",
coordinate: infiniteLoop)
}
.mapStyle(.standard(elevation: .realistic))
.mapControls {
MapUserLocationButton()
MapCompass()
MapScaleView()
}
}
}Marker and Annotation
Marker与Annotation
swift
// Balloon marker -- simplest way to pin a location
Marker("Cafe", systemImage: "cup.and.saucer.fill", coordinate: cafeCoord)
.tint(.brown)
// Annotation -- custom SwiftUI view at a coordinate
Annotation("You", coordinate: userCoord, anchor: .bottom) {
Image(systemName: "figure.wave")
.padding(6)
.background(.blue.gradient, in: .circle)
.foregroundStyle(.white)
}swift
// 气球标记——钉选位置的最简单方式
Marker("Cafe", systemImage: "cup.and.saucer.fill", coordinate: cafeCoord)
.tint(.brown)
// Annotation——指定坐标上的自定义SwiftUI视图
Annotation("You", coordinate: userCoord, anchor: .bottom) {
Image(systemName: "figure.wave")
.padding(6)
.background(.blue.gradient, in: .circle)
.foregroundStyle(.white)
}Overlays: Polyline, Polygon, Circle
覆盖层:折线、多边形、圆形
swift
Map {
// Polyline from coordinates
MapPolyline(coordinates: routeCoords)
.stroke(.blue, lineWidth: 4)
// Polygon (area highlight)
MapPolygon(coordinates: parkBoundary)
.foregroundStyle(.green.opacity(0.3))
.stroke(.green, lineWidth: 2)
// Circle (radius around a point)
MapCircle(center: storeCoord, radius: 500)
.foregroundStyle(.red.opacity(0.15))
.stroke(.red, lineWidth: 1)
}swift
Map {
// 基于坐标的折线
MapPolyline(coordinates: routeCoords)
.stroke(.blue, lineWidth: 4)
// 多边形(区域高亮)
MapPolygon(coordinates: parkBoundary)
.foregroundStyle(.green.opacity(0.3))
.stroke(.green, lineWidth: 2)
// 圆形(点位周边半径范围)
MapCircle(center: storeCoord, radius: 500)
.foregroundStyle(.red.opacity(0.15))
.stroke(.red, lineWidth: 1)
}Camera Position
相机位置
MapCameraPositionswift
// Center on a region
@State private var position: MapCameraPosition = .region(
MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 37.334, longitude: -122.009),
span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
)
)
// Follow user location
@State private var position: MapCameraPosition = .userLocation(fallback: .automatic)
// Specific camera angle (3D perspective)
@State private var position: MapCameraPosition = .camera(
MapCamera(centerCoordinate: applePark, distance: 1000, heading: 90, pitch: 60)
)
// Frame specific items
position = .item(MKMapItem.forCurrentLocation())
position = .rect(MKMapRect(...))MapCameraPositionswift
// 居中展示某个区域
@State private var position: MapCameraPosition = .region(
MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 37.334, longitude: -122.009),
span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
)
)
// 跟随用户位置
@State private var position: MapCameraPosition = .userLocation(fallback: .automatic)
// 指定相机角度(3D视角)
@State private var position: MapCameraPosition = .camera(
MapCamera(centerCoordinate: applePark, distance: 1000, heading: 90, pitch: 60)
)
// 框选特定元素
position = .item(MKMapItem.forCurrentLocation())
position = .rect(MKMapRect(...))Map Style
地图样式
swift
.mapStyle(.standard) // Default road map
.mapStyle(.standard(elevation: .realistic, showsTraffic: true))
.mapStyle(.imagery) // Satellite
.mapStyle(.imagery(elevation: .realistic)) // 3D satellite
.mapStyle(.hybrid) // Satellite + labels
.mapStyle(.hybrid(elevation: .realistic, showsTraffic: true))swift
.mapStyle(.standard) // 默认道路地图
.mapStyle(.standard(elevation: .realistic, showsTraffic: true))
.mapStyle(.imagery) // 卫星图
.mapStyle(.imagery(elevation: .realistic)) // 3D卫星图
.mapStyle(.hybrid) // 卫星图+标注
.mapStyle(.hybrid(elevation: .realistic, showsTraffic: true))Map Interaction Modes
地图交互模式
swift
.mapInteractionModes(.all) // Default: pan, zoom, rotate, pitch
.mapInteractionModes(.pan) // Pan only
.mapInteractionModes([.pan, .zoom]) // Pan and zoom
.mapInteractionModes([]) // Static map (no interaction)swift
.mapInteractionModes(.all) // 默认:平移、缩放、旋转、俯仰
.mapInteractionModes(.pan) // 仅允许平移
.mapInteractionModes([.pan, .zoom]) // 允许平移和缩放
.mapInteractionModes([]) // 静态地图(无交互)Map Selection
地图选中
swift
@State private var selectedMarker: MKMapItem?
Map(selection: $selectedMarker) {
ForEach(places) { place in
Marker(place.name, coordinate: place.coordinate)
.tag(place.mapItem) // Tag must match selection type
}
}
.onChange(of: selectedMarker) { _, newValue in
guard let item = newValue else { return }
// React to selection
}swift
@State private var selectedMarker: MKMapItem?
Map(selection: $selectedMarker) {
ForEach(places) { place in
Marker(place.name, coordinate: place.coordinate)
.tag(place.mapItem) // 标签必须与选中类型匹配
}
}
.onChange(of: selectedMarker) { _, newValue in
guard let item = newValue else { return }
// 处理选中事件
}CoreLocation Modern API
CoreLocation现代API
CLLocationUpdate.liveUpdates() (iOS 17+)
CLLocationUpdate.liveUpdates()(iOS 17+)
Replace callbacks with a single async sequence.
Each iteration yields a containing an optional .
CLLocationManagerDelegateCLLocationUpdateCLLocationswift
import CoreLocation
@Observable
final class LocationTracker: @unchecked Sendable {
var currentLocation: CLLocation?
private var updateTask: Task<Void, Never>?
func startTracking() {
updateTask = Task {
let updates = CLLocationUpdate.liveUpdates()
for try await update in updates {
guard let location = update.location else { continue }
// Filter by horizontal accuracy
guard location.horizontalAccuracy < 50 else { continue }
await MainActor.run {
self.currentLocation = location
}
}
}
}
func stopTracking() {
updateTask?.cancel()
updateTask = nil
}
}使用单个异步序列替代回调。每次迭代返回一个包含可选的对象。
CLLocationManagerDelegateCLLocationCLLocationUpdateswift
import CoreLocation
@Observable
final class LocationTracker: @unchecked Sendable {
var currentLocation: CLLocation?
private var updateTask: Task<Void, Never>?
func startTracking() {
updateTask = Task {
let updates = CLLocationUpdate.liveUpdates()
for try await update in updates {
guard let location = update.location else { continue }
// 按水平精度过滤
guard location.horizontalAccuracy < 50 else { continue }
await MainActor.run {
self.currentLocation = location
}
}
}
}
func stopTracking() {
updateTask?.cancel()
updateTask = nil
}
}CLServiceSession (iOS 18+)
CLServiceSession(iOS 18+)
Declare authorization requirements for a feature's lifetime. Hold a reference
to the session for as long as you need location services.
swift
// When-in-use authorization with full accuracy preference
let session = CLServiceSession(
authorization: .whenInUse,
fullAccuracyPurposeKey: "NearbySearchPurpose"
)
// Hold `session` as a stored property; release it when done.On iOS 18+, and take an implicit
if you do not create one explicitly. Create one explicitly
when you need authorization or full accuracy.
CLLocationUpdate.liveUpdates()CLMonitorCLServiceSession.always声明功能生命周期内的权限要求。只要你需要使用位置服务,就需要持有该会话的引用。
swift
// 使用时授权,优先获取全精度
let session = CLServiceSession(
authorization: .whenInUse,
fullAccuracyPurposeKey: "NearbySearchPurpose"
)
// 将`session`作为存储属性持有;使用完毕后释放在iOS 18+系统上,如果你没有显式创建,和会隐式获取一个。当你需要授权或全精度时,请显式创建。
CLServiceSessionCLLocationUpdate.liveUpdates()CLMonitor.alwaysAuthorization Flow
授权流程
swift
// Info.plist keys (required):
// NSLocationWhenInUseUsageDescription
// NSLocationAlwaysAndWhenInUseUsageDescription (only if .always needed)
// Check authorization and guide user to Settings when denied
func checkAuthorization() {
let manager = CLLocationManager()
switch manager.authorizationStatus {
case .notDetermined:
manager.requestWhenInUseAuthorization()
case .denied, .restricted:
// Show alert with Settings deep link
if let url = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url)
}
case .authorizedWhenInUse, .authorizedAlways:
break // Ready to use
@unknown default:
break
}
}swift
// Info.plist必填项:
// NSLocationWhenInUseUsageDescription
// NSLocationAlwaysAndWhenInUseUsageDescription(仅当需要.always权限时配置)
// 检查授权状态,权限被拒绝时引导用户前往设置
func checkAuthorization() {
let manager = CLLocationManager()
switch manager.authorizationStatus {
case .notDetermined:
manager.requestWhenInUseAuthorization()
case .denied, .restricted:
// 展示携带设置页 deep link 的弹窗
if let url = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url)
}
case .authorizedWhenInUse, .authorizedAlways:
break // 可正常使用
@unknown default:
break
}
}Geocoding
地理编码
CLGeocoder (iOS 8+)
CLGeocoder(iOS 8+)
swift
let geocoder = CLGeocoder()
// Forward geocoding: address string -> coordinates
let placemarks = try await geocoder.geocodeAddressString("1 Apple Park Way, Cupertino")
if let location = placemarks.first?.location {
print(location.coordinate) // CLLocationCoordinate2D
}
// Reverse geocoding: coordinates -> placemark
let location = CLLocation(latitude: 37.3349, longitude: -122.0090)
let placemarks = try await geocoder.reverseGeocodeLocation(location)
if let placemark = placemarks.first {
let address = [placemark.name, placemark.locality, placemark.administrativeArea]
.compactMap { $0 }
.joined(separator: ", ")
}swift
let geocoder = CLGeocoder()
// 正向地理编码:地址字符串 -> 坐标
let placemarks = try await geocoder.geocodeAddressString("1 Apple Park Way, Cupertino")
if let location = placemarks.first?.location {
print(location.coordinate) // CLLocationCoordinate2D
}
// 逆向地理编码:坐标 -> 地标
let location = CLLocation(latitude: 37.3349, longitude: -122.0090)
let placemarks = try await geocoder.reverseGeocodeLocation(location)
if let placemark = placemarks.first {
let address = [placemark.name, placemark.locality, placemark.administrativeArea]
.compactMap { $0 }
.joined(separator: ", ")
}MKGeocodingRequest and MKReverseGeocodingRequest (iOS 26+)
MKGeocodingRequest与MKReverseGeocodingRequest(iOS 26+)
New MapKit-native geocoding that returns with richer data and
/ for flexible address formatting.
MKMapItemMKAddressMKAddressRepresentationsswift
@available(iOS 26, *)
func reverseGeocode(location: CLLocation) async throws -> MKMapItem? {
guard let request = MKReverseGeocodingRequest(location: location) else {
return nil
}
let mapItems = try await request.mapItems
return mapItems.first
}
@available(iOS 26, *)
func forwardGeocode(address: String) async throws -> [MKMapItem] {
guard let request = MKGeocodingRequest(addressString: address) else { return [] }
return try await request.mapItems
}MapKit原生的新型地理编码接口,返回携带更丰富数据的,以及支持灵活地址格式化的/。
MKMapItemMKAddressMKAddressRepresentationsswift
@available(iOS 26, *)
func reverseGeocode(location: CLLocation) async throws -> MKMapItem? {
guard let request = MKReverseGeocodingRequest(location: location) else {
return nil
}
let mapItems = try await request.mapItems
return mapItems.first
}
@available(iOS 26, *)
func forwardGeocode(address: String) async throws -> [MKMapItem] {
guard let request = MKGeocodingRequest(addressString: address) else { return [] }
return try await request.mapItems
}Search
搜索
MKLocalSearchCompleter (Autocomplete)
MKLocalSearchCompleter(自动补全)
swift
@Observable
final class SearchCompleter: NSObject, MKLocalSearchCompleterDelegate {
var results: [MKLocalSearchCompletion] = []
var query: String = "" { didSet { completer.queryFragment = query } }
private let completer = MKLocalSearchCompleter()
override init() {
super.init()
completer.delegate = self
completer.resultTypes = [.address, .pointOfInterest]
}
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
results = completer.results
}
func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
results = []
}
}swift
@Observable
final class SearchCompleter: NSObject, MKLocalSearchCompleterDelegate {
var results: [MKLocalSearchCompletion] = []
var query: String = "" { didSet { completer.queryFragment = query } }
private let completer = MKLocalSearchCompleter()
override init() {
super.init()
completer.delegate = self
completer.resultTypes = [.address, .pointOfInterest]
}
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
results = completer.results
}
func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
results = []
}
}MKLocalSearch (Full Search)
MKLocalSearch(完整搜索)
swift
func search(for completion: MKLocalSearchCompletion) async throws -> [MKMapItem] {
let request = MKLocalSearch.Request(completion: completion)
request.resultTypes = [.pointOfInterest, .address]
let search = MKLocalSearch(request: request)
let response = try await search.start()
return response.mapItems
}
// Search by natural language query within a region
func searchNearby(query: String, region: MKCoordinateRegion) async throws -> [MKMapItem] {
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = query
request.region = region
let search = MKLocalSearch(request: request)
let response = try await search.start()
return response.mapItems
}swift
func search(for completion: MKLocalSearchCompletion) async throws -> [MKMapItem] {
let request = MKLocalSearch.Request(completion: completion)
request.resultTypes = [.pointOfInterest, .address]
let search = MKLocalSearch(request: request)
let response = try await search.start()
return response.mapItems
}
// 在指定区域内通过自然语言查询搜索
func searchNearby(query: String, region: MKCoordinateRegion) async throws -> [MKMapItem] {
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = query
request.region = region
let search = MKLocalSearch(request: request)
let response = try await search.start()
return response.mapItems
}Directions
路线规划
swift
func getDirections(from source: MKMapItem, to destination: MKMapItem,
transport: MKDirectionsTransportType = .automobile) async throws -> MKRoute? {
let request = MKDirections.Request()
request.source = source
request.destination = destination
request.transportType = transport
let directions = MKDirections(request: request)
let response = try await directions.calculate()
return response.routes.first
}swift
func getDirections(from source: MKMapItem, to destination: MKMapItem,
transport: MKDirectionsTransportType = .automobile) async throws -> MKRoute? {
let request = MKDirections.Request()
request.source = source
request.destination = destination
request.transportType = transport
let directions = MKDirections(request: request)
let response = try await directions.calculate()
return response.routes.first
}Display Route on Map
在地图上展示路线
swift
@State private var route: MKRoute?
Map {
if let route {
MapPolyline(route.polyline)
.stroke(.blue, lineWidth: 5)
}
Marker("Start", coordinate: startCoord)
Marker("End", coordinate: endCoord)
}
.task {
route = try? await getDirections(from: startItem, to: endItem)
}swift
@State private var route: MKRoute?
Map {
if let route {
MapPolyline(route.polyline)
.stroke(.blue, lineWidth: 5)
}
Marker("起点", coordinate: startCoord)
Marker("终点", coordinate: endCoord)
}
.task {
route = try? await getDirections(from: startItem, to: endItem)
}ETA Calculation
预计到达时间计算
swift
func getETA(from source: MKMapItem, to destination: MKMapItem) async throws -> TimeInterval {
let request = MKDirections.Request()
request.source = source
request.destination = destination
let directions = MKDirections(request: request)
let response = try await directions.calculateETA()
return response.expectedTravelTime
}swift
func getETA(from source: MKMapItem, to destination: MKMapItem) async throws -> TimeInterval {
let request = MKDirections.Request()
request.source = source
request.destination = destination
let directions = MKDirections(request: request)
let response = try await directions.calculateETA()
return response.expectedTravelTime
}Cycling Directions (iOS 26+)
骑行路线规划(iOS 26+)
swift
@available(iOS 26, *)
func getCyclingDirections(to destination: MKMapItem) async throws -> MKRoute? {
let request = MKDirections.Request()
request.source = MKMapItem.forCurrentLocation()
request.destination = destination
request.transportType = .cycling
let directions = MKDirections(request: request)
let response = try await directions.calculate()
return response.routes.first
}swift
@available(iOS 26, *)
func getCyclingDirections(to destination: MKMapItem) async throws -> MKRoute? {
let request = MKDirections.Request()
request.source = MKMapItem.forCurrentLocation()
request.destination = destination
request.transportType = .cycling
let directions = MKDirections(request: request)
let response = try await directions.calculate()
return response.routes.first
}PlaceDescriptor (iOS 26+)
PlaceDescriptor(iOS 26+)
Create rich place references from coordinates or addresses without needing a
Place ID. Requires .
import GeoToolboxswift
@available(iOS 26, *)
func lookupPlace(name: String, coordinate: CLLocationCoordinate2D) async throws -> MKMapItem {
let descriptor = PlaceDescriptor(
representations: [.coordinate(coordinate)],
commonName: name
)
let request = MKMapItemRequest(placeDescriptor: descriptor)
return try await request.mapItem
}无需地点ID即可通过坐标或地址创建丰富的地点引用。需要导入。
GeoToolboxswift
@available(iOS 26, *)
func lookupPlace(name: String, coordinate: CLLocationCoordinate2D) async throws -> MKMapItem {
let descriptor = PlaceDescriptor(
representations: [.coordinate(coordinate)],
commonName: name
)
let request = MKMapItemRequest(placeDescriptor: descriptor)
return try await request.mapItem
}Common Mistakes
常见错误
DON'T: Request upfront — users distrust broad permissions.
DO: Start with , escalate to only when the user enables a background feature.
.authorizedAlways.requestWhenInUseAuthorization().alwaysDON'T: Use for simple location fetches on iOS 17+.
DO: Use async stream for cleaner, more concise code.
CLLocationManagerDelegateCLLocationUpdate.liveUpdates()DON'T: Keep location updates running when the map/view is not visible (drains battery).
DO: Use in SwiftUI so updates cancel automatically on disappear.
.task { }DON'T: Force-unwrap properties — they are all optional.
DO: Use nil-coalescing: .
CLPlacemarkplacemark.locality ?? "Unknown"DON'T: Fire queries on every keystroke.
DO: Debounce with + .
MKLocalSearchCompleter.task(id: searchText)Task.sleep(for: .milliseconds(300))DON'T: Silently fail when location authorization is denied.
DO: Detect status and show an alert with a Settings deep link.
.deniedDON'T: Assume geocoding always succeeds — handle empty results and network errors.
不要: 一开始就请求权限——用户不信任宽泛的权限申请。
应当: 先使用,仅当用户启用后台功能时再升级为权限。
.authorizedAlways.requestWhenInUseAuthorization().always不要: 在iOS 17+系统上使用实现简单的位置获取。
应当: 使用异步流实现更简洁清晰的代码。
CLLocationManagerDelegateCLLocationUpdate.liveUpdates()不要: 在地图/视图不可见时仍保持位置更新运行(会消耗电量)。
应当: 在SwiftUI中使用,这样视图消失时更新会自动取消。
.task { }不要: 强制解包属性——它们都是可选类型。
应当: 使用空合运算符:。
CLPlacemarkplacemark.locality ?? "未知"不要: 用户每次按键都触发查询。
应当: 使用 + 做防抖处理。
MKLocalSearchCompleter.task(id: searchText)Task.sleep(for: .milliseconds(300))不要: 位置授权被拒绝时静默失败。
应当: 检测到状态时展示弹窗,附带跳转至设置页的链接。
.denied不要: 假定地理编码总是成功——请处理空结果和网络错误。
Review Checklist
审查清单
- Info.plist has with specific reason
NSLocationWhenInUseUsageDescription - Authorization denial handled with Settings deep link
- task cancelled when not needed (battery)
CLLocationUpdate - Location accuracy appropriate for the use case
- Map annotations use data with stable IDs
Identifiable - Geocoding errors handled (network failure, no results)
- Search completer input debounced
- limited to 20 conditions, instance kept alive
CLMonitor - Background location uses
CLBackgroundActivitySession - Map tested with VoiceOver
- Map annotation view models and location UI updates are -isolated
@MainActor
- Info.plist中已配置并说明具体使用原因
NSLocationWhenInUseUsageDescription - 权限被拒绝时已处理,提供跳转至设置页的链接
- 不需要时已取消任务(节省电量)
CLLocationUpdate - 位置精度适配当前使用场景
- 地图标注使用带有稳定ID的数据
Identifiable - 已处理地理编码错误(网络失败、无结果)
- 搜索补全输入已做防抖处理
- 限制在20个条件以内,实例已被持有不会被释放
CLMonitor - 后台定位使用
CLBackgroundActivitySession - 地图已通过VoiceOver无障碍测试
- 地图标注视图模型和位置UI更新已通过隔离
@MainActor
References
参考文献
- — Map setup, annotations, search, routes, clustering, Look Around, snapshots.
references/mapkit-patterns.md - — CLLocationUpdate, CLMonitor, CLServiceSession, background location, testing.
references/corelocation-patterns.md - apple-docs MCP: ,
/documentation/mapkit/map/documentation/corelocation/cllocationupdate
- —— 地图配置、标注、搜索、路线、聚类、环顾功能、快照。
references/mapkit-patterns.md - —— CLLocationUpdate、CLMonitor、CLServiceSession、后台定位、测试。
references/corelocation-patterns.md - Apple官方文档MCP:、
/documentation/mapkit/map/documentation/corelocation/cllocationupdate