如何在MapKit中让MKPointAnnotation固定在地图中心(滚动时保持)
我之前做过类似的需求,刚好可以给你分享下最优的实现方式——其实不用执着于MKPointAnnotation,换个思路用子视图固定在地图中心会更简单高效,完全能达到Ola那种实时固定的效果。
MKPointAnnotation是和地理坐标绑定的,当地图滚动时,它的位置会跟着地图的地理区域变化,所以没法直接固定在屏幕中心。正确的做法是:把定位标记做成地图视图的子视图,通过Auto Layout固定在屏幕中心,然后实时获取这个标记对应的地理坐标,这样不管用户怎么滚动、缩放地图,标记都稳稳在中间。
步骤1:创建并添加中心标记子视图
先做一个你想要的标记样式(比如Ola的圆形标记),然后把它添加到MKMapView上,用约束固定在中心:
// 自定义标记视图,比如一个蓝色的圆点 class CenterMarkerView: UIView { override init(frame: CGRect) { super.init(frame: frame) setupView() } required init?(coder: NSCoder) { super.init(coder: coder) setupView() } private func setupView() { backgroundColor = .systemBlue layer.cornerRadius = frame.width / 2 layer.shadowColor = UIColor.black.cgColor layer.shadowOpacity = 0.3 layer.shadowOffset = CGSize(width: 0, height: 2) } } // 在你的ViewController里添加标记 let centerMarker = CenterMarkerView(frame: CGRect(x: 0, y: 0, width: 30, height: 30)) centerMarker.translatesAutoresizingMaskIntoConstraints = false mapView.addSubview(centerMarker) // 添加约束,固定在地图中心 NSLayoutConstraint.activate([ centerMarker.centerXAnchor.constraint(equalTo: mapView.centerXAnchor), centerMarker.centerYAnchor.constraint(equalTo: mapView.centerYAnchor), centerMarker.widthAnchor.constraint(equalToConstant: 30), centerMarker.heightAnchor.constraint(equalToConstant: 30) ])
步骤2:实时获取标记对应的地理坐标
现在标记已经固定在屏幕中心了,接下来要在地图滚动、缩放时,实时获取这个位置对应的地理坐标。我们可以利用MKMapViewDelegate的回调,或者直接在需要的地方转换坐标:
方式1:通过地图代理回调实时更新
实现mapView(_:regionDidChangeAnimated:)方法,每次地图区域变化时,转换坐标:
extension YourViewController: MKMapViewDelegate { func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { // 将标记的中心位置转换成地理坐标 let markerCenter = centerMarker.center let coordinate = mapView.convert(markerCenter, toCoordinateFrom: mapView) // 这里就可以用这个coordinate做你需要的操作了,比如更新搜索结果、定位等 print("当前中心坐标:\(coordinate.latitude), \(coordinate.longitude)") } }
方式2:主动获取坐标(比如点击确认按钮时)
如果不需要实时监听,只是在用户点击“确认位置”之类的按钮时获取坐标,直接调用转换方法即可:
@IBAction func confirmLocationTapped(_ sender: UIButton) { let markerCenter = centerMarker.center let coordinate = mapView.convert(markerCenter, toCoordinateFrom: mapView) // 处理这个坐标,比如保存、搜索周边等 }
如果你一定要用MKPointAnnotation(不推荐)
如果因为某些原因必须用MKPointAnnotation,那只能通过实时更新它的坐标来模拟固定效果,但这种方式性能不如子视图,因为滚动时会频繁触发更新:
// 先创建Annotation并添加到地图 let centerAnnotation = MKPointAnnotation() mapView.addAnnotation(centerAnnotation) // 用CADisplayLink来实时更新坐标 var displayLink: CADisplayLink? func startUpdatingCenterAnnotation() { displayLink = CADisplayLink(target: self, selector: #selector(updateCenterAnnotation)) displayLink?.add(to: .main, forMode: .common) } func stopUpdatingCenterAnnotation() { displayLink?.invalidate() displayLink = nil } @objc private func updateCenterAnnotation() { // 获取地图中心的屏幕坐标,转换成地理坐标 let mapCenterPoint = CGPoint(x: mapView.bounds.midX, y: mapView.bounds.midY) let centerCoordinate = mapView.convert(mapCenterPoint, toCoordinateFrom: mapView) centerAnnotation.coordinate = centerCoordinate } // 记得在视图消失时停止displayLink override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) stopUpdatingCenterAnnotation() }
注意:这种方式在地图滚动时会每秒调用60次左右,可能会有轻微的性能损耗,所以优先推荐子视图的方案。
额外优化点
- 如果你的标记需要和地图的旋转同步(比如地图旋转时标记也跟着转),可以监听地图的
camera变化,同步更新标记的transform:
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { let cameraRotation = mapView.camera.heading centerMarker.transform = CGAffineTransform(rotationAngle: -cameraRotation * .pi / 180) }
这样就能完美实现Ola那种中心标记固定的效果了,不管用户怎么操作地图,标记都始终在屏幕中间,而且能实时获取到精确的地理坐标。
内容的提问来源于stack exchange,提问作者Tushar Katyal

