iOS16及以上MKMapView随动缩放圆圈闪烁问题求助
问题:MKMapView中心圆圈随地图移动/缩放时闪烁
我需要在MKMapView上添加一个始终处于地图中心的圆圈,要求随用户平移地图同步移动,随地图缩放同步缩放。目前通过获取地图中心坐标实现了功能,但每次坐标变化时都要移除并重新添加MKCircle overlay,导致圆圈出现明显闪烁。
期望目标
在MKMapView上方添加可随地图同步移动/缩放的圆圈/标注,无闪烁、流畅显示。
现有代码
地图初始化与手势添加
func makeUIView(context: Context) -> MKMapView { let mapView = MKMapView() mapView.delegate = context.coordinator mapView.isUserInteractionEnabled = true mapView.showsUserLocation = true mapView.showsCompass = false let panGesture = UIPanGestureRecognizer(target: context.coordinator, action: #selector(context.coordinator.addPinBasedOnGesture(_:))) panGesture.delegate = context.coordinator mapView.addGestureRecognizer(panGesture) return mapView }
更新圆圈坐标的方法
func updateMovingCircle(regionToUpdate: MKCoordinateRegion, view: MKMapView) { DispatchQueue.main.async { addMovingCircle(to: view, movingLocation: CLLocationCoordinate2D(latitude: regionToUpdate.center.latitude, longitude: regionToUpdate.center.longitude), radius: radius) } }
添加/移除圆圈Overlay
func addMovingCircle(to view: MKMapView, movingLocation: CLLocationCoordinate2D, radius: Int) { for overlay in overlays where overlay.title == "1" { view.removeOverlay(overlay) } let radius: Double = Double(radius) * 1.5 let movingCircle = MKCircle(center: movingLocation, radius: radius * 1000) movingCircle.title = "1" view.addOverlay(movingCircle) }
手势识别代理方法
@objc func addPinBasedOnGesture(_ gestureRecognizer: UIGestureRecognizer) { if let mapView = gestureRecognizer.view as? MKMapView { mapViewController.updateMovingCircle(regionToUpdate: mapView.region, view: mapView) } } func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { return true } func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { return true } func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true }
Overlay渲染器
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { if let circleOverlay = overlay as? MKCircle { let circleRenderer = MKCircleRenderer(overlay: circleOverlay) circleRenderer.strokeColor = UIColor(Color.SG.purple) circleRenderer.lineWidth = 2 circleRenderer.lineDashPattern = [2, 5] return circleRenderer } return MKOverlayRenderer() }
解决方案
闪烁的核心原因是频繁移除并重新添加Overlay,导致视图重绘出现断层。以下两种方案均可彻底解决问题:
方案1:复用现有MKCircle,避免重复创建
直接更新已存在的MKCircle属性,而非删除重建:
- 在类中保存MKCircle的全局引用:
private var centerCircle: MKCircle?
- 修改
addMovingCircle方法,复用已有Circle:
func addMovingCircle(to view: MKMapView, movingLocation: CLLocationCoordinate2D, radius: Int) { let targetRadius: Double = Double(radius) * 1.5 * 1000 if let existingCircle = centerCircle { // 更新中心坐标与半径 existingCircle.coordinate = movingLocation existingCircle.radius = targetRadius // 通知地图刷新该Overlay view.reloadOverlay(existingCircle) } else { // 首次创建Circle centerCircle = MKCircle(center: movingLocation, radius: targetRadius) centerCircle?.title = "1" view.addOverlay(centerCircle!) } }
- 替换手势监听为地图原生代理方法(覆盖所有地图操作场景):
在协调器中实现MKMapViewDelegate的区域变化监听:
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { mapViewController.updateMovingCircle(regionToUpdate: mapView.region, view: mapView) }
之后可移除自定义的平移手势,因为地图自身的平移、缩放操作都会触发该代理方法。
方案2:使用UIView直接叠加(更流畅)
跳过Overlay,直接在地图上层添加UIView,通过约束固定中心,随缩放调整大小:
- 初始化地图时添加中心圆圈视图:
func makeUIView(context: Context) -> MKMapView { let mapView = MKMapView() mapView.delegate = context.coordinator mapView.isUserInteractionEnabled = true mapView.showsUserLocation = true mapView.showsCompass = false // 创建中心圆圈视图 let centerCircleView = UIView() centerCircleView.tag = 1001 centerCircleView.layer.borderColor = UIColor(Color.SG.purple).cgColor centerCircleView.layer.borderWidth = 2 centerCircleView.layer.cornerRadius = 40 centerCircleView.layer.borderDashPattern = [2,5] as [NSNumber] centerCircleView.translatesAutoresizingMaskIntoConstraints = false mapView.addSubview(centerCircleView) // 约束固定在地图中心 NSLayoutConstraint.activate([ centerCircleView.centerXAnchor.constraint(equalTo: mapView.centerXAnchor), centerCircleView.centerYAnchor.constraint(equalTo: mapView.centerYAnchor), centerCircleView.widthAnchor.constraint(equalToConstant: 80), centerCircleView.heightAnchor.constraint(equalToConstant: 80) ]) return mapView }
- 在区域变化代理方法中调整视图大小:
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { guard let centerCircleView = mapView.viewWithTag(1001) else { return } // 根据地图缩放比例计算目标尺寸 let metersPerPoint = mapView.convert(CLLocationDistance(1), toPointTo: mapView.centerCoordinate) let targetRadiusMeters = Double(radius) * 1.5 * 1000 let targetDiameterPoints = targetRadiusMeters * 2 / metersPerPoint // 更新视图大小与圆角 centerCircleView.bounds = CGRect(x: 0, y: 0, width: targetDiameterPoints, height: targetDiameterPoints) centerCircleView.layer.cornerRadius = targetDiameterPoints / 2 }
该方案无需操作Overlay,直接通过UIView布局实现,完全无闪烁问题。
内容的提问来源于stack exchange,提问作者Bug
相关产品推荐
相关产品推荐

