mcpField
A
axiom-mapkit-diag

axiom-mapkit-diag

github.com/charleswiltgen开发1.2k85 次安装今天更新
代码仓库

关于 axiom-mapkit-diag

面向 iOS 开发者的 MapKit 症状式排查技能,通过诊断路径将常见地图问题对应到检查项和修复方法。

主要功能

  1. 01标注与坐标问题排查
  2. 02地图区域更新循环诊断
  3. 03标注性能与聚类建议
  4. 04覆盖层渲染检查
  5. 05搜索与定位诊断

适用场景

  1. 01排查地图标注未显示
  2. 02解决地图区域更新循环
  3. 03优化含大量标注的地图
  4. 04诊断搜索或用户位置故障

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-mapkit-diag

安装后在 Claude Code 中输入/axiom-mapkit-diag确认已加载。

详细介绍

MapKit 诊断

基于症状排查 MapKit 问题。找到你遇到的症状,然后按照诊断路径排查。

相关技能

  • axiom-mapkit — 模式、决策树、反模式
  • axiom-mapkit-ref — API 参考、代码示例

快速参考

症状首先检查常见修复方法
注释未显示坐标值(纬度/经度是否颠倒?)验证坐标,检查 viewFor 代理方法
地图区域跳动/循环updateUIView 守卫条件添加区域相等性检查
注释很多时速度变慢注释数量、视图复用启用聚类,实现视图复用
聚类不起作用是否设置了 clusteringIdentifier?为所有视图设置相同的标识符
覆盖层未渲染渲染器代理方法返回正确的 MKOverlayRenderer 子类
搜索没有结果resultTypes、区域偏置设置适当的 resultTypes 和区域
用户位置未显示授权状态先请求 CLLocationManager 授权
坐标显示错误纬度/经度顺序MapKit 使用(纬度、经度)——请检查数据源

症状 1:注释未显示

决策树
Q1: Are coordinates valid?
├─ 0,0 or NaN → Data source returning default/empty values
│   Fix: Validate coordinates before adding annotations
│   Debug: print("\(annotation.coordinate.latitude), \(annotation.coordinate.longitude)")
│
└─ Valid numbers → Check next

Q2: Are lat/lng swapped?
├─ YES (common with GeoJSON which uses [longitude, latitude]) → Swap values
│   GeoJSON: [lng, lat] — MapKit: CLLocationCoordinate2D(latitude:, longitude:)
│   Fix: CLLocationCoordinate2D(latitude: json[1], longitude: json[0])
│
└─ NO → Check next

Q3: (MKMapView) Is mapView(_:viewFor:) delegate returning nil for your annotations?
├─ Not implemented → System uses default pin (should appear)
├─ Returns nil → System uses default pin (should appear)
├─ Returns wrong view → Check implementation
│
└─ Check delegate is set

Q4: (MKMapView) Is delegate set?
├─ NO → mapView.delegate = self (or context.coordinator in UIViewRepresentable)
│   Without delegate: default pins appear. But if viewFor returns nil, check annotation type
│
└─ YES → Check next

Q5: (SwiftUI) Are annotations in Map content builder?
├─ NO → Annotations must be inside Map { ... } content closure
│   Fix: Map(position: $pos) { Marker("Name", coordinate: coord) }
│
└─ YES → Check next

Q6: Is the map region showing the annotation coordinates?
├─ Map centered elsewhere → Adjust camera/region to include annotation coordinates
│   Debug: Compare mapView.region with annotation coordinates
│   Fix: Use .automatic camera position or set region to fit annotations
│
└─ Region includes annotations → Check displayPriority

Q7: (MKMapView) Is displayPriority too low?
├─ .defaultLow → System may hide annotations at certain zoom levels
│   Fix: view.displayPriority = .required for must-show annotations
│
└─ .required → Annotation should appear — file a bug report with minimal repro

症状 2:地图区域跳动/无限循环

决策树
Q1: (UIViewRepresentable) Is setRegion called in updateUIView without guard?
├─ YES → Classic infinite loop:
│   1. SwiftUI state changes → updateUIView called
│   2. updateUIView calls setRegion
│   3. setRegion triggers regionDidChangeAnimated delegate
│   4. Delegate updates SwiftUI state → back to step 1
│
│   Fix: Guard against unnecessary updates
│   if mapView.region.center.latitude != region.center.latitude
│      || mapView.region.center.longitude != region.center.longitude {
│       mapView.setRegion(region, animated: true)
│   }
│
│   Alternative: Use a flag in coordinator
│   coordinator.isUpdating = true
│   mapView.setRegion(region, animated: true)
│   coordinator.isUpdating = false
│   // In regionDidChangeAnimated: guard !isUpdating
│
└─ NO → Check next

Q2: Are multiple state sources fighting over the region?
├─ YES → Two bindings or state variables controlling the same region
│   Fix: Single source of truth for camera position
│   One @State var cameraPosition, not two conflicting values
│
└─ NO → Check next

Q3: (SwiftUI) Is MapCameraPosition properly bound?
├─ Using .constant() or recreating position on each render → Camera resets
│   Fix: @State private var cameraPosition: MapCameraPosition = .automatic
│   Use the binding: Map(position: $cameraPosition)
│
└─ Properly bound → Check next

Q4: Animation conflict?
├─ Using animated: true in updateUIView alongside SwiftUI animations → Double animation
│   Fix: Avoid animated: true in updateUIView, or disable SwiftUI animation for map
│
└─ NO → Check next

Q5: Is onMapCameraChange triggering state updates that move the camera?
├─ YES → Camera change → callback → state change → camera change
│   Fix: Only update non-camera state in the callback
│   Don't set cameraPosition inside onMapCameraChange
│
└─ NO → Check delegate implementation for unintended state mutations

症状 3:性能问题

决策树
Q1: How many annotations?
├─ > 500 without clustering → Enable clustering
│   SwiftUI: .mapItemClusteringIdentifier("poi")
│   MKMapView: view.clusteringIdentifier = "poi"
│
├─ > 1000 → Consider visible-region filtering
│   Only load annotations within mapView.region
│   Use .onMapCameraChange to fetch when user scrolls
│
└─ < 500 → Check next

Q2: (MKMapView) Using dequeueReusableAnnotationView?
├─ NO → Every annotation creates a new view → memory spike
│   Fix: Register view class and dequeue in delegate
│   mapView.register(MKMarkerAnnotationView.self, forAnnotationViewWithReuseIdentifier: "marker")
│
└─ YES → Check next

Q3: Complex custom annotation views?
├─ YES → Rich SwiftUI views or complex UIViews per annotation
│   Fix: Pre-render to UIImage for MKAnnotationView.image
│   Or simplify to MKMarkerAnnotationView with glyph
│
└─ NO → Check next

Q4: Overlays with many coordinates?
├─ YES → Polylines/polygons with 10K+ points
│   Fix: Simplify geometry (Douglas-Peucker algorithm)
│   Or render at reduced detail for zoomed-out views
│
└─ NO → Check next

Q5: Geocoding in a loop?
├─ YES → CLGeocoder has rate limit (~1/second)
│   Fix: Batch geocoding, throttle requests, cache results
│   Use MKLocalSearch for batch lookups instead of per-item geocoding
│
└─ NO → Profile with Instruments → Time Profiler for CPU, Allocations for memory

症状 4:聚类不起作用

决策树
Q1: Is clusteringIdentifier set on annotation views?
├─ NO → Clustering requires an identifier on each annotation view
│   MKMapView: view.clusteringIdentifier = "poi" in viewFor delegate
│   SwiftUI: .mapItemClusteringIdentifier("poi") on content
│
└─ YES → Check next

Q2: Are ALL relevant views using the SAME identifier?
├─ NO → Different identifiers = different cluster groups
│   Fix: Use consistent identifier for annotations that should cluster together
│
└─ YES → Check next

Q3: (MKMapView) Is mapView(_:clusterAnnotationForMemberAnnotations:) needed?
├─ Not implemented → System creates default cluster
│   If you need custom cluster appearance, implement this delegate method
│
└─ Implemented → Check return value

Q4: Too few annotations in visible area?
├─ YES → Clustering only activates when annotations physically overlap
│   At low zoom (city level), 10 annotations might cluster
│   At high zoom (street level), same 10 might all be visible individually
│
└─ NO → Check next

Q5: (MKMapView) Are annotation views registered?
├─ NO → Register both individual and cluster view classes
│   mapView.register(MKMarkerAnnotationView.self, forAnnotationViewWithReuseIdentifier: "marker")
│
└─ YES → Verify viewFor delegate handles both MKClusterAnnotation and individual annotations

症状 5:覆盖层未渲染

决策树
Q1: (MKMapView) Is mapView(_:rendererFor:) delegate method implemented?
├─ NO → Overlays require a renderer — without this delegate method, nothing renders
│   Fix: Implement the delegate method, return appropriate renderer subclass
│
└─ YES → Check next

Q2: Is the correct renderer subclass returned?
├─ MKCircle → MKCircleRenderer
│   MKPolyline → MKPolylineRenderer
│   MKPolygon → MKPolygonRenderer
│   MKTileOverlay → MKTileOverlayRenderer
│   Mismatch → Crash or silent failure
│
└─ Correct → Check next

Q3: Is renderer styled?
├─ No strokeColor/fillColor/lineWidth set → Renderer exists but invisible
│   Fix: Set at minimum strokeColor and lineWidth
│   renderer.strokeColor = .systemBlue
│   renderer.lineWidth = 2
│
└─ Styled → Check next

Q4: Overlay level wrong?
├─ .aboveRoads → Overlay may be behind labels (hard to see)
│   Try: mapView.addOverlay(overlay, level: .aboveLabels)
│
└─ Check overlay coordinates match visible region

Q5: (SwiftUI) Using MapCircle/MapPolyline without styling?
├─ No .foregroundStyle or .stroke → May render transparent
│   Fix: MapCircle(center: coord, radius: 500)
│            .foregroundStyle(.blue.opacity(0.3))
│            .stroke(.blue, lineWidth: 2)
│
└─ Styled → Check coordinates are within visible map region

症状 6:搜索/路线规划失败

决策树
Q1: Network available?
├─ NO → MapKit search requires network connectivity
│   Fix: Check URLSession connectivity or NWPathMonitor
│
└─ YES → Check next

Q2: resultTypes too restrictive?
├─ Only .physicalFeature but searching for "Starbucks" → No results
│   Fix: Use .pointOfInterest for businesses, .address for streets
│   Or combine: [.pointOfInterest, .address]
│
└─ Appropriate → Check next

Q3: Region bias missing?
├─ NO region set → Results may be from anywhere in the world
│   Fix: request.region = mapView.region (or visible region)
│   This biases results to what the user can see
│
└─ Region set → Check next

Q4: Natural language query format?
├─ Structured format (lat/lng, codes) → Won't parse
│   Good: "coffee shops near San Francisco"
│   Good: "123 Main St"
│   Bad: "lat:37.7 lng:-122.4 coffee"
│   Bad: "POI_TYPE=cafe"
│
└─ Natural language → Check next

Q5: Rate limited?
├─ Getting errors after many requests → Apple rate-limits MapKit search
│   Fix: Throttle searches, use MKLocalSearchCompleter for autocomplete
│   Don't fire MKLocalSearch on every keystroke
│
└─ NO → Check next

Q6: (Directions) Source and destination valid?
├─ source or destination is nil → Request will fail
│   Fix: Verify both are valid MKMapItem instances
│   MKMapItem.forCurrentLocation() requires location authorization
│
└─ Both valid → Check transportType availability
    Transit directions not available in all regions
    Walking/driving available globally

症状 7:用户位置未显示

决策树
Q1: What is CLLocationManager.authorizationStatus?
├─ .notDetermined → Authorization never requested
│   Fix: Request authorization first, then enable user location
│   CLServiceSession(authorization: .whenInUse)
│
├─ .denied → User denied location access
│   Fix: Show UI explaining value, link to Settings
│
├─ .restricted → Parental controls block access
│   Fix: Inform user, cannot override
│
└─ .authorizedWhenInUse / .authorizedAlways → Check next

Q2: (MKMapView) Is showsUserLocation set to true?
├─ NO → mapView.showsUserLocation = true
│
└─ YES → Check next

Q3: (SwiftUI) Using UserAnnotation() in Map content?
├─ NO → Add UserAnnotation() inside Map { ... }
│
└─ YES → Check next

Q4: Running in Simulator?
├─ YES, no custom location set → Simulator doesn't have GPS
│   Fix: Debug menu → Location → Custom Location (or Apple/City Bicycle Ride/etc.)
│   Xcode: Debug → Simulate Location → pick a location
│
└─ Physical device → Check next

Q5: MapKit implicitly requests authorization — was it previously denied?
├─ MapKit shows no prompt if already denied
│   Check: Settings → Privacy & Security → Location Services → Your App
│   If "Never": User must manually re-enable
│
└─ Authorized → Check if location services enabled system-wide
    Settings → Privacy & Security → Location Services → toggle at top

Q6: Location icon appearing but blue dot not on screen?
├─ User is outside the visible map region
│   Fix: Use MapCameraPosition.userLocation(fallback: .automatic)
│   Or add MapUserLocationButton() in .mapControls
│
└─ See axiom-core-location-diag for deeper location troubleshooting

症状 8:坐标系混淆

常见的坐标错误会导致注释出现在错误的位置。

MapKit 与 GeoJSON
系统顺序示例
MapKit (CLLocationCoordinate2D)纬度、经度CLLocationCoordinate2D(latitude: 37.77, longitude: -122.42)
GeoJSON经度、纬度[-122.42, 37.77]
Google Maps纬度、经度与 MapKit 相同
PostGIS ST_MakePoint经度、纬度与 GeoJSON 相同

最常见的坐标错误:解析 GeoJSON 时颠倒纬度和经度。

// ❌ WRONG: Using GeoJSON order directly
let coord = CLLocationCoordinate2D(
    latitude: geoJson[0],    // This is longitude!
    longitude: geoJson[1]    // This is latitude!
)

// ✅ RIGHT: GeoJSON is [lng, lat], MapKit wants (lat, lng)
let coord = CLLocationCoordinate2D(
    latitude: geoJson[1],
    longitude: geoJson[0]
)
MKMapPoint 与 CLLocationCoordinate2D
  • CLLocationCoordinate2D — 地理坐标(以度为单位的纬度/经度)
  • MKMapPoint — 用于平面地图渲染的投影坐标
  • 转换:使用 MKMapPoint(coordinate),以及 MKMapPoint 的 coordinate 属性
  • 切勿将 MKMapPoint 的 x/y 用作纬度/经度——它们属于完全不同的数值空间
验证
func isValidCoordinate(_ coord: CLLocationCoordinate2D) -> Bool {
    coord.latitude >= -90 && coord.latitude <= 90
        && coord.longitude >= -180 && coord.longitude <= 180
        && !coord.latitude.isNaN && !coord.longitude.isNaN
}

如果纬度 > 90 或经度 > 180,坐标很可能颠倒了或格式不正确。


控制台调试

MapKit 日志
# View MapKit-related logs
log stream --predicate 'subsystem == "com.apple.MapKit"' --level debug

# Filter for your app
log stream --predicate 'process == "YourApp" AND (subsystem == "com.apple.MapKit" OR subsystem == "com.apple.CoreLocation")'
常见控制台消息
消息含义
No renderer for overlay缺少 rendererFor 代理方法
Reuse identifier not registered调用 dequeue 前先调用 register
CLLocationManager authorizationStatus is denied用户拒绝了位置权限

资源

WWDC:2023-10043、2024-10094

文档:/mapkit、/mapkit/mklocalsearch

技能:axiom-mapkit、axiom-mapkit-ref、axiom-core-location-diag

评论

支持 Markdown