Swift实现带标注自定义地图:能否复刻DayZDB地图功能?
如何用Swift实现带标注的自定义地图
嘿,这个需求我刚好有实践经验,给你梳理两种靠谱的实现方案,完全能达到你想要的效果:
方案一:用UIScrollView实现自定义静态地图(最贴合你说的示例效果)
这个方案直接基于UIScrollView搭建,完美支持加载本地图片、捏合缩放、在指定位置放置标注,和你提到的示例功能几乎一致,实现起来也最简单。
核心步骤:
- 搭建缩放基础:把地图图片放到
UIImageView里,再嵌套进UIScrollView,设置缩放范围,实现UIScrollViewDelegate的缩放代理方法。 - 添加标注/自定义视图:根据原始图片的坐标计算标注在滚动视图中的位置,把标注添加到合适的容器里(随图片缩放或固定大小)。
核心代码示例:
import UIKit class CustomMapVC: UIViewController, UIScrollViewDelegate { private let scrollView = UIScrollView() private let mapImageView = UIImageView(image: UIImage(named: "your_custom_map")) // 原始图片上的标注位置(比如图片宽高1000x800,这里的坐标是基于原图的) private let annotationPoints = [CGPoint(x: 200, y: 300), CGPoint(x: 500, y: 600)] override func viewDidLoad() { super.viewDidLoad() setupScrollView() addMapAnnotations() } private func setupScrollView() { view.addSubview(scrollView) scrollView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ scrollView.topAnchor.constraint(equalTo: view.topAnchor), scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor), scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor), scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) // 配置缩放参数 scrollView.delegate = self scrollView.minimumZoomScale = 1.0 scrollView.maximumZoomScale = 5.0 scrollView.showsScrollIndicators = false // 添加地图图片 scrollView.addSubview(mapImageView) mapImageView.translatesAutoresizingMaskIntoConstraints = false guard let mapSize = mapImageView.image?.size else { return } NSLayoutConstraint.activate([ mapImageView.topAnchor.constraint(equalTo: scrollView.topAnchor), mapImageView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor), mapImageView.widthAnchor.constraint(equalToConstant: mapSize.width), mapImageView.heightAnchor.constraint(equalToConstant: mapSize.height) ]) } private func addMapAnnotations() { guard let mapSize = mapImageView.image?.size else { return } for point in annotationPoints { // 自定义标注视图(这里用按钮示例,你可以换成任何UIView) let annotationView = UIButton(type: .custom) annotationView.setImage(UIImage(named: "annotation_icon"), for: .normal) annotationView.frame.size = CGSize(width: 32, height: 32) // 计算标注在图片上的位置(居中对齐) annotationView.frame.origin = CGPoint( x: point.x - annotationView.frame.width/2, y: point.y - annotationView.frame.height/2 ) // 👉 如果要让标注随图片一起缩放,就添加到mapImageView上 mapImageView.addSubview(annotationView) // 👉 如果要让标注固定大小不缩放,就添加到scrollView或view上,然后在缩放时更新位置(下面有示例) // scrollView.addSubview(annotationView) // annotationView.tag = 100 // 给标注打标签方便后续识别 } } // MARK: - UIScrollViewDelegate func viewForZooming(in scrollView: UIScrollView) -> UIView? { return mapImageView } // 👉 如果标注固定大小,需要实现这个方法更新位置 /* func scrollViewDidZoom(_ scrollView: UIScrollView) { updateFixedSizeAnnotations() } private func updateFixedSizeAnnotations() { let zoomScale = scrollView.zoomScale let contentOffset = scrollView.contentOffset for subview in scrollView.subviews where subview.tag == 100 { guard let index = annotationPoints.firstIndex(where: { // 匹配标注对应的原始坐标,这里可以用tag关联索引,更高效 abs($0.x - (subview.frame.origin.x + subview.frame.width/2)/zoomScale) < 1 && abs($0.y - (subview.frame.origin.y + subview.frame.height/2)/zoomScale) < 1 }) else { continue } let originalPoint = annotationPoints[index] // 转换原始坐标到当前缩放后的scrollView坐标 let newX = originalPoint.x * zoomScale - subview.frame.width/2 + contentOffset.x let newY = originalPoint.y * zoomScale - subview.frame.height/2 + contentOffset.y subview.frame.origin = CGPoint(x: newX, y: newY) } } */ }
方案二:用MapKit实现自定义地图(适合带地理坐标的场景)
如果你需要结合真实地理数据,或者地图是由多块瓦片拼接而成,MapKit会是更合适的选择。它自带成熟的地图交互(缩放、平移),还能轻松实现标注弹窗、拖拽等扩展功能。
核心步骤:
- 加载自定义底图:通过
MKOverlay把自定义图片添加到MapKit中,设置对应的地理范围。 - 添加标注:用
MKPointAnnotation定义标注的地理坐标,自定义MKAnnotationView展示样式。
核心代码示例:
import UIKit import MapKit class MapKitCustomMapVC: UIViewController, MKMapViewDelegate { private let mapView = MKMapView() // 自定义地图的地理范围(这里用MKMapRect模拟,你可以换成真实的经纬度范围) private let customMapBounds = MKMapRect(x: 0, y: 0, width: 1000, height: 800) override func viewDidLoad() { super.viewDidLoad() setupMapView() addCustomMapOverlay() addMapAnnotations() } private func setupMapView() { view.addSubview(mapView) mapView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ mapView.topAnchor.constraint(equalTo: view.topAnchor), mapView.leadingAnchor.constraint(equalTo: view.leadingAnchor), mapView.trailingAnchor.constraint(equalTo: view.trailingAnchor), mapView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) mapView.delegate = self // 设置地图显示范围为自定义底图的范围 mapView.setRegion(MKCoordinateRegion(customMapBounds), animated: false) } private func addCustomMapOverlay() { guard let mapImage = UIImage(named: "your_custom_map") else { return } // 创建自定义图片Overlay let imageOverlay = MKImageOverlay(image: mapImage, mapRect: customMapBounds) mapView.addOverlay(imageOverlay) } private func addMapAnnotations() { // 把原始图片坐标转换为地理坐标 let annotation1 = MKPointAnnotation() annotation1.coordinate = customMapBounds.coordinate(at: CGPoint(x: 200, y: 300)) annotation1.title = "资源点1" let annotation2 = MKPointAnnotation() annotation2.coordinate = customMapBounds.coordinate(at: CGPoint(x: 500, y: 600)) annotation2.title = "资源点2" mapView.addAnnotations([annotation1, annotation2]) } // MARK: - MKMapViewDelegate func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { if let imageOverlay = overlay as? MKImageOverlay { return MKImageOverlayRenderer(overlay: imageOverlay) } return MKOverlayRenderer(overlay: overlay) } func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { guard annotation is MKPointAnnotation else { return nil } let reuseID = "CustomAnnotation" var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseID) as? MKPinAnnotationView if annotationView == nil { annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseID) annotationView?.canShowCallout = true // 点击显示弹窗 annotationView?.pinTintColor = .red } else { annotationView?.annotation = annotation } return annotationView } } // 扩展MKMapRect,方便图片坐标转地理坐标 extension MKMapRect { func coordinate(at point: CGPoint) -> CLLocationCoordinate2D { let longitude = origin.longitude + (point.x / width) * span.longitudeDelta let latitude = origin.latitude - (point.y / height) * span.latitudeDelta return CLLocationCoordinate2D(latitude: latitude, longitude: longitude) } var span: MKCoordinateSpan { let topLeft = origin.coordinate let bottomRight = MKMapPoint(x: origin.x + width, y: origin.y + height).coordinate return MKCoordinateSpan( latitudeDelta: topLeft.latitude - bottomRight.latitude, longitudeDelta: bottomRight.longitude - topLeft.longitude ) } }
方案选择建议
- 如果只是单张大图的静态地图,优先选UIScrollView方案,代码简单,性能好,完全满足你要的捏合缩放、标注功能。
- 如果需要结合地理数据、瓦片地图,或者要利用MapKit的成熟交互(比如标注拖拽、路线规划),选MapKit方案更合适。
内容的提问来源于stack exchange,提问作者Nxt97
相关产品推荐
相关产品推荐

