You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 15:07:18