如何在SwiftUI的MapKit Map View中添加MKOverlay覆盖层
解决方案
SwiftUI 内置的 Map 视图在 iOS 17 之前未提供原生的覆盖层配置参数,要实现 MKOverlay 相关能力,可根据项目最低支持版本选择以下两种方案:
方案1:兼容 iOS 14 及以上版本(UIKit 封装实现)
通过 UIViewRepresentable 封装 UIKit 原生的 MKMapView,保留原有交互逻辑的同时支持添加任意类型的覆盖层:
首先是封装的自定义视图代码:
import SwiftUI import MapKit struct CustomMapView: UIViewRepresentable { @Binding var region: MKCoordinateRegion let annotationItems: [City] let overlays: [MKOverlay] func makeUIView(context: Context) -> MKMapView { let mapView = MKMapView() mapView.delegate = context.coordinator mapView.setRegion(region, animated: false) // 添加覆盖层 mapView.addOverlays(overlays) return mapView } func updateUIView(_ uiView: MKMapView, context: Context) { // 同步区域更新 if uiView.region.center.latitude != region.center.latitude || uiView.region.center.longitude != region.center.longitude || uiView.region.span.latitudeDelta != region.span.latitudeDelta || uiView.region.span.longitudeDelta != region.span.longitudeDelta { uiView.setRegion(region, animated: true) } // 同步标注更新 uiView.removeAnnotations(uiView.annotations) let annotations = annotationItems.map { city -> MKPointAnnotation in let anno = MKPointAnnotation() anno.coordinate = city.coordinate return anno } uiView.addAnnotations(annotations) // 同步覆盖层更新 uiView.removeOverlays(uiView.overlays) uiView.addOverlays(overlays) } func makeCoordinator() -> Coordinator { Coordinator() } class Coordinator: NSObject, MKMapViewDelegate { // 覆盖层渲染配置 func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { // 瓦片覆盖层渲染 if let tileOverlay = overlay as? MKTileOverlay { return MKTileOverlayRenderer(overlay: tileOverlay) } // 多边形覆盖层渲染 if let polygon = overlay as? MKPolygon { let renderer = MKPolygonRenderer(overlay: polygon) renderer.fillColor = .blue.withAlphaComponent(0.3) renderer.strokeColor = .blue renderer.lineWidth = 2 return renderer } // 折线覆盖层渲染 if let polyline = overlay as? MKPolyline { let renderer = MKPolylineRenderer(overlay: polyline) renderer.strokeColor = .red renderer.lineWidth = 3 return renderer } return MKOverlayRenderer(overlay: overlay) } // 标注渲染配置 func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { let pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "cityPin") pinView.tintColor = .red return pinView } // 同步区域拖拽后的绑定值 func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { DispatchQueue.main.async { if let binding = mapView.value(forKey: "regionBinding") as? Binding<MKCoordinateRegion> { binding.wrappedValue = mapView.region } } } } }
然后替换原有 ContentView 中的系统 Map 即可:
struct ContentView: View { @State private var cities: [City] = [ City(coordinate: .init(latitude: 54.8985, longitude: 23.9036)), City(coordinate: .init(latitude: 54.6872, longitude: 25.2797)), City(coordinate: .init(latitude: 55.7033, longitude: 21.1443)) ] @State private var region = MKCoordinateRegion( center: CLLocationCoordinate2D(latitude: 54.8, longitude: 23.8), span: MKCoordinateSpan( latitudeDelta: 5, longitudeDelta: 5 ) ) // 配置需要添加的覆盖层,可同时添加多个不同类型的覆盖层 private var overlays: [MKOverlay] { let tileOverlay = MKTileOverlay(urlTemplate: "https://services.arcgisonline.com/ArcGIS/rest/services/USA_Topo_Maps/MapServer/tile/{z}/{y}/{x}.jpg") // 示例多边形,可根据需求替换 let polygon = MKPolygon(coordinates: [ CLLocationCoordinate2D(latitude: 54.8985, longitude: 23.9036), CLLocationCoordinate2D(latitude: 54.6872, longitude: 25.2797), CLLocationCoordinate2D(latitude: 55.7033, longitude: 21.1443) ], count: 3) return [tileOverlay, polygon] } var body: some View { ZStack { // 替换为自定义的CustomMapView CustomMapView(region: $region, annotationItems: cities, overlays: overlays) .ignoresSafeArea() Circle() .fill(Color.blue) .opacity(0.3) .frame(width: 32, height: 32) VStack { Spacer() HStack { Spacer() Button { self.cities.append(City(coordinate: self.region.center)) } label: { Image(systemName: "plus") } .padding() .background(.black.opacity(0.75)) .foregroundColor(.white) .font(.title) .clipShape(Circle()) .padding(.trailing) } } } } }
方案2:最低支持 iOS 17 及以上版本(原生API实现)
iOS 17 之后 SwiftUI 新增了覆盖层相关的原生支持,无需封装UIKit组件,直接使用内置API即可:
struct ContentView: View { @State private var cities: [City] = [ City(coordinate: .init(latitude: 54.8985, longitude: 23.9036)), City(coordinate: .init(latitude: 54.6872, longitude: 25.2797)), City(coordinate: .init(latitude: 55.7033, longitude: 21.1443)) ] @State private var position: MapCameraPosition = .region(MKCoordinateRegion( center: CLLocationCoordinate2D(latitude: 54.8, longitude: 23.8), span: MKCoordinateSpan( latitudeDelta: 5, longitudeDelta: 5 ) )) // 瓦片覆盖层 private let tileOverlay = MKTileOverlay(urlTemplate: "https://services.arcgisonline.com/ArcGIS/rest/services/USA_Topo_Maps/MapServer/tile/{z}/{y}/{x}.jpg") // 示例多边形 private let polygon = MKPolygon(coordinates: [ CLLocationCoordinate2D(latitude: 54.8985, longitude: 23.9036), CLLocationCoordinate2D(latitude: 54.6872, longitude: 25.2797), CLLocationCoordinate2D(latitude: 55.7033, longitude: 21.1443) ], count: 3) var body: some View { ZStack { Map(position: $position) { // 标注 ForEach(cities) { city in MapPin(coordinate: city.coordinate, tint: .red) } // 瓦片覆盖层 MapOverlay(tileOverlay) // 多边形覆盖层 MapPolygon(polygon) .foregroundStyle(.blue.opacity(0.3)) .stroke(.blue, lineWidth: 2) } .ignoresSafeArea() Circle() .fill(Color.blue) .opacity(0.3) .frame(width: 32, height: 32) VStack { Spacer() HStack { Spacer() Button { if let region = position.region { self.cities.append(City(coordinate: region.center)) } } label: { Image(systemName: "plus") } .padding() .background(.black.opacity(0.75)) .foregroundColor(.white) .font(.title) .clipShape(Circle()) .padding(.trailing) } } } } }
内容的提问来源于stack exchange,提问作者Crow
相关产品推荐
相关产品推荐

