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

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:终极快照方案(完全消除卡顿)

如果轻量优化后还有掉帧,可以用快照替代地图参与动画,动画结束后再显示真实地图,百分百流畅:

  1. 先在EventViewController中新增快照存储属性:
// 存储拖拽过程中的地图快照
var mapSnapshotView: UIView?
  1. 让弹出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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 09:36:04