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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 04:20:34