iOS 17 SwiftUI Map无UIKit实现POI标注聚类方法咨询
iOS 17 SwiftUI Map POI聚类方案
目前iOS 17的SwiftUI原生Map组件确实没有官方提供POI聚类的直接API,不过不用完全从零实现聚类算法——可以通过封装UIKit的MKMapView来复用它成熟的聚类能力,这样比自己写算法更高效可靠。
实现步骤
- 用
UIViewRepresentable把MKMapView封装成SwiftUI可用的组件 - 实现
MKMapViewDelegate启用聚类逻辑,自定义聚类标注样式 - 在SwiftUI视图中使用封装后的Map组件,传入POI数据
完整代码示例
import MapKit import SwiftUI // 封装带聚类的MKMapView到SwiftUI struct ClusterMap: UIViewRepresentable { @Binding var cameraPosition: MapCameraPosition let annotations: [SampleData] func makeUIView(context: Context) -> MKMapView { let mapView = MKMapView() mapView.delegate = context.coordinator mapView.showsUserLocation = false // 启用聚类 mapView.clusteringEnabled = true // 设置初始区域 if case .region(let region) = cameraPosition { mapView.setRegion(region, animated: false) } // 添加标注 mapView.addAnnotations(annotations) return mapView } func updateUIView(_ uiView: MKMapView, context: Context) { // 同步相机位置 if case .region(let region) = cameraPosition { uiView.setRegion(region, animated: true) } // 更新标注(处理数据变化) let existingIds = Set(uiView.annotations.compactMap { ($0 as? SampleData)?.id }) let newAnnotations = annotations.filter { !existingIds.contains($0.id) } let removedAnnotations = uiView.annotations.compactMap { $0 as? SampleData }.filter { !annotations.contains($0) } uiView.addAnnotations(newAnnotations) uiView.removeAnnotations(removedAnnotations) } func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, MKMapViewDelegate { var parent: ClusterMap init(_ parent: ClusterMap) { self.parent = parent } // 自定义标注视图:区分普通POI和聚类组 func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { guard let cluster = annotation as? MKClusterAnnotation else { // 普通POI标注 let identifier = "POIMarker" var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKMarkerAnnotationView if annotationView == nil { annotationView = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifier) annotationView?.markerTintColor = .blue annotationView?.glyphImage = UIImage(systemName: "mappin") } else { annotationView?.annotation = annotation } return annotationView } // 聚类组标注 let identifier = "ClusterMarker" var clusterView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKMarkerAnnotationView if clusterView == nil { clusterView = MKMarkerAnnotationView(annotation: cluster, reuseIdentifier: identifier) clusterView?.markerTintColor = .red clusterView?.glyphText = "\(cluster.memberAnnotations.count)" } else { clusterView?.annotation = cluster clusterView?.glyphText = "\(cluster.memberAnnotations.count)" } return clusterView } // 同步地图区域变化到SwiftUI的Binding变量 func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { parent.cameraPosition = .region(mapView.region) } } } // 让SampleData遵守MKAnnotation协议,适配MKMapView要求 extension SampleData: MKAnnotation { var title: String? { "" } var subtitle: String? { nil } } struct ContentView: View { @State var mapCameraPosition: MapCameraPosition = .region(MKCoordinateRegion( center: CLLocationCoordinate2D(latitude: 43.64422936785126, longitude: 142.39329541313924), span: MKCoordinateSpan(latitudeDelta: 1.5, longitudeDelta: 2))) var body: some View { ClusterMap(cameraPosition: $mapCameraPosition, annotations: data) .edgesIgnoringSafeArea(.all) } } struct SampleData: Identifiable { var id = UUID() var latitude: Double var longitude: Double var coordinate: CLLocationCoordinate2D { CLLocationCoordinate2D( latitude: latitude, longitude: longitude) } } var data = [ SampleData(latitude: 43.70564024126748, longitude: 142.37968945214223), SampleData(latitude: 43.81257464206404, longitude: 142.82112322464369), SampleData(latitude: 43.38416585162576, longitude: 141.7252598737476), SampleData(latitude: 45.29168643283501, longitude: 141.95286751470724), SampleData(latitude: 45.49261392585982, longitude: 141.9343973160499), SampleData(latitude: 44.69825427301145, longitude: 141.91227845284203) ] struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
关键说明
- 让
SampleData遵守MKAnnotation协议,才能被MKMapView识别为标注 - 通过
mapView.clusteringEnabled = true开启原生聚类,MKMapView会自动根据地图缩放级别智能分组POI - 自定义
MKMarkerAnnotationView分别处理普通标注和聚类标注,聚类标注直接显示组内POI数量 - 封装的
ClusterMap通过Binding同步SwiftUI的相机位置与MKMapView的区域变化,保持视图状态一致
内容的提问来源于stack exchange,提问作者Tbi
相关产品推荐
相关产品推荐

