iOS15中UISheetPresentationController内MKMapView调整尺寸如何保证动画流畅
问题核心原因
这个卡顿的核心原因是MKMapView在自身bounds变化时,会默认自动调整显示区域、触发实时重绘,和UISheetPresentationController的尺寸切换动画不同步,导致视觉上掉帧。以下是可直接落地的实现方案,不需要修改你原本的相对高度需求:
方案1:轻量优化(覆盖90%场景)
优先用这个方案,改动极小,不需要修改原有逻辑:
class EventViewController: UIViewController { let viewModel: EventViewModel // 提前存储固定的地图显示区域,避免尺寸变化时自动调整 private let fixedMapRegion: MKCoordinateRegion init(viewModel: EventViewModel) { self.viewModel = viewModel // 初始化时就计算好固定区域,不要在懒加载里处理 self.fixedMapRegion = MKCoordinateRegion( center: viewModel.event.structuredLocation!.geoLocation!.coordinate, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01) ) super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private lazy var mapView = MKMapView().with { $0.isZoomEnabled = true $0.isScrollEnabled = true // 开启异步渲染,把重绘逻辑放到后台线程,不阻塞动画 $0.isAsyncRenderingEnabled = true // 初始化时设置固定区域,关闭不必要的动画 $0.setRegion(fixedMapRegion, animated: false) $0.addAnnotation(MKPointAnnotation().with { $0.coordinate = viewModel.event.structuredLocation!.geoLocation!.coordinate }) } override func viewDidLoad() { self.title = viewModel.event.title self.view.backgroundColor = .systemBackground view.addSubview(mapView) mapView.with { $0.translatesAutoresizingMaskIntoConstraints = false $0.topAnchor.constraint(equalTo: view.topAnchor).isActive = true $0.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true $0.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true // 用bottom锚点和centerY绑定,和你原有0.5倍高度的效果完全一致,布局计算更稳定 $0.bottomAnchor.constraint(equalTo: view.centerYAnchor).isActive = true } } // 布局变化时强制重设固定区域,避免地图自动调整显示范围触发额外重绘 override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() mapView.setRegion(fixedMapRegion, animated: false) } }
方案2:终极快照方案(完全消除卡顿)
如果轻量优化后还有掉帧,可以用快照替代地图参与动画,动画结束后再显示真实地图,百分百流畅:
- 先在
EventViewController中新增快照存储属性:
// 存储拖拽过程中的地图快照 var mapSnapshotView: UIView?
- 让弹出
EventViewController的父控制器遵循UISheetPresentationControllerDelegate,修改弹出逻辑:
func showEventDetails(for event: EKEvent) { let viewModel = EventViewModel(event: event, baseCoordinator: self) let eventViewController = EventViewController(viewModel: viewModel) eventViewController.modalPresentationStyle = .pageSheet if let sheet = eventViewController.sheetPresentationController { sheet.detents = [.medium(), .large()] sheet.prefersScrollingExpandsWhenScrolledToEdge = true sheet.prefersGrabberVisible = true // 绑定代理监听拖拽状态 sheet.delegate = self } navigationController.present(eventViewController, animated: true, completion: nil) } // 监听拖拽开始,生成静态快照盖住地图 func sheetPresentationControllerWillBeginDragging(_ sheet: UISheetPresentationController) { guard let eventVC = sheet.presentedViewController as? EventViewController else { return } eventVC.mapView.isUserInteractionEnabled = false // 生成当前地图的静态快照 let renderer = UIGraphicsImageRenderer(size: eventVC.mapView.bounds.size) let snapshotImage = renderer.image { context in eventVC.mapView.layer.render(in: context.cgContext) } let snapshotView = UIImageView(image: snapshotImage) snapshotView.frame = eventVC.mapView.frame eventVC.view.insertSubview(snapshotView, aboveSubview: eventVC.mapView) eventVC.mapSnapshotView = snapshotView eventVC.mapView.alpha = 0 } // 监听尺寸切换完成,移除快照恢复真实地图 func sheetPresentationControllerDidChangeSelectedDetentIdentifier(_ sheet: UISheetPresentationController) { guard let eventVC = sheet.presentedViewController as? EventViewController else { return } UIView.animate(withDuration: 0.3) { eventVC.mapView.alpha = 1 } completion: { _ in eventVC.mapSnapshotView?.removeFromSuperview() eventVC.mapSnapshotView = nil eventVC.mapView.isUserInteractionEnabled = true } }
内容的提问来源于stack exchange,提问作者Niklas
相关产品推荐
相关产品推荐

