SwiftUI中MapKit重写loadTile实现本地动态自定义瓦片
在MapKit中本地动态生成自定义瓦片的实现
核心实现思路
你需要在loadTile(at:result:)方法里完成三个核心步骤:
- 动态绘制256x256的瓦片图像
- 将图像转换为
Data格式(PNG/JPEG均可) - 通过闭包返回结果(成功返回Data,失败返回Error)
具体代码实现
方法1:用Core Graphics直接绘制瓦片
这种方式适合纯代码绘图,性能表现更优:
import MapKit import UIKit class TileOverlay: MKTileOverlay { override func loadTile(at path: MKTileOverlayPath, result: @escaping (Data?, Error?) -> Void) { // 固定瓦片尺寸为256x256,MapKit要求必须符合该规格 let tileSize = CGSize(width: 256, height: 256) // 创建绘图上下文 UIGraphicsBeginImageContextWithOptions(tileSize, true, 0) guard let context = UIGraphicsGetCurrentContext() else { result(nil, NSError(domain: "TileError", code: -1, userInfo: [NSLocalizedDescriptionKey: "无法创建绘图上下文"])) return } // 自定义绘图逻辑:根据瓦片路径绘制差异化内容 let coordinate = self.coordinate(for: path) let tileInfoText = "Z:\(path.z)\nX:\(path.x)\nY:\(path.y)\nLat:\(coordinate.latitude)\nLon:\(coordinate.longitude)" // 填充背景色:基于X/Y坐标生成交替色块 UIColor(red: CGFloat(path.x % 2) * 0.2, green: CGFloat(path.y % 2) * 0.2, blue: 0.5, alpha: 1).setFill() context.fill(CGRect(origin: .zero, size: tileSize)) // 绘制瓦片信息文字 let textAttributes: [NSAttributedString.Key: Any] = [ .font: UIFont.systemFont(ofSize: 16), .foregroundColor: UIColor.white ] tileInfoText.draw(at: CGPoint(x: 10, y: 10), withAttributes: textAttributes) // 获取生成的图像 guard let image = UIGraphicsGetImageFromCurrentImageContext() else { UIGraphicsEndImageContext() result(nil, NSError(domain: "TileError", code: -2, userInfo: [NSLocalizedDescriptionKey: "无法生成瓦片图像"])) return } UIGraphicsEndImageContext() // 将图像转为PNG格式Data(也可使用jpegData(compressionQuality:)生成JPEG) guard let imageData = image.pngData() else { result(nil, NSError(domain: "TileError", code: -3, userInfo: [NSLocalizedDescriptionKey: "无法将图像转为Data"])) return } // 返回最终结果 result(imageData, nil) } }
方法2:用SwiftUI View生成瓦片
如果习惯用SwiftUI的声明式语法构建瓦片内容,可以将View渲染为图像:
import MapKit import UIKit import SwiftUI class TileOverlay: MKTileOverlay { override func loadTile(at path: MKTileOverlayPath, result: @escaping (Data?, Error?) -> Void) { // 定义SwiftUI瓦片内容View let tileView = TileView(path: path) .frame(width: 256, height: 256) // 将SwiftUI View渲染为UIImage let renderer = ImageRenderer(content: tileView) renderer.scale = UIScreen.main.scale // 保证瓦片高清显示 guard let image = renderer.uiImage else { result(nil, NSError(domain: "TileError", code: -1, userInfo: [NSLocalizedDescriptionKey: "无法渲染SwiftUI View为图像"])) return } // 转为Data并返回 guard let imageData = image.pngData() else { result(nil, NSError(domain: "TileError", code: -2, userInfo: [NSLocalizedDescriptionKey: "无法将图像转为Data"])) return } result(imageData, nil) } } // 自定义SwiftUI瓦片View struct TileView: View { let path: MKTileOverlayPath var body: some View { ZStack { // 背景色:根据瓦片坐标生成交替效果 Color(red: Double(path.x % 2) * 0.2, green: Double(path.y % 2) * 0.2, blue: 0.5) // 显示瓦片坐标与地理信息 VStack(alignment: .leading, spacing: 8) { Text("缩放级别: \(path.z)") Text("X坐标: \(path.x)") Text("Y坐标: \(path.y)") Text("纬度: \(coordinate.latitude)") Text("经度: \(coordinate.longitude)") } .foregroundColor(.white) .font(.system(size: 16)) .padding(10) } } // 从瓦片路径转换为地理坐标 private var coordinate: CLLocationCoordinate2D { let overlay = TileOverlay() return overlay.coordinate(for: path) } }
在SwiftUI中集成自定义瓦片
最后在你的地图视图中添加并配置这个瓦片覆盖层:
import SwiftUI import MapKit struct MapView: UIViewRepresentable { func makeUIView(context: Context) -> MKMapView { let mapView = MKMapView() mapView.delegate = context.coordinator // 创建并添加自定义瓦片覆盖层 let tileOverlay = TileOverlay() tileOverlay.canReplaceMapContent = false // 设为true可覆盖默认地图底图 mapView.addOverlay(tileOverlay, level: .aboveRoads) // 可调整瓦片显示层级 // 设置初始地图区域 let initialCoordinate = CLLocationCoordinate2D(latitude: 39.9042, longitude: 116.4074) mapView.setRegion(MKCoordinateRegion(center: initialCoordinate, span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)), animated: true) return mapView } func updateUIView(_ uiView: MKMapView, context: Context) {} func makeCoordinator() -> Coordinator { Coordinator() } class Coordinator: NSObject, MKMapViewDelegate { func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { guard let tileOverlay = overlay as? TileOverlay else { return MKOverlayRenderer(overlay: overlay) } return MKTileOverlayRenderer(tileOverlay: tileOverlay) } } } struct MapView_Previews: PreviewProvider { static var previews: some View { MapView() } }
注意事项
- 瓦片必须严格为256x256像素,否则MapKit无法正确渲染
loadTile(at:result:)方法在后台线程执行,Core Graphics绘图操作线程安全;若使用SwiftUI渲染,ImageRenderer会自动处理线程适配- 可根据
MKTileOverlayPath的z(缩放级别)、x、y参数生成差异化内容,比如加载本地矢量数据、绘制自定义图形等 - 完善错误处理,避免因瓦片生成失败导致地图加载异常
内容的提问来源于stack exchange,提问作者Math Rules
相关产品推荐
相关产品推荐

