Xcode 11模态ViewController中CollectionViewCell滚动尺寸变形问题排查
嗨,我之前也踩过一模一样的坑!这个问题的核心其实是Xcode11引入的新模态展示样式(比如pageSheet)在拖拽关闭过程中,控制器的View尺寸会被动态调整,而你的UICollectionViewFlowLayout依赖collectionView.bounds计算Cell尺寸,这就导致了布局计算的冲突,加上滚动越界的弹性效果,直接触发了那个烦人的FlowLayout错误,同时Cell也跟着变形。
接下来给你几个经过验证的解决方案,按优先级尝试:
1. 强制在View尺寸变化时刷新CollectionView布局
当模态控制器被拖拽时,View的bounds会持续变化,但FlowLayout不会自动重新计算尺寸。你需要主动监听布局变化,强制刷新:
override func viewDidLoad() { super.viewDidLoad() // 添加监听,捕捉View布局变化 NotificationCenter.default.addObserver(self, selector: #selector(refreshCollectionViewLayout), name: UIViewController.viewWillLayoutSubviewsNotification, object: nil) } @objc private func refreshCollectionViewLayout() { // 强制让FlowLayout重新计算所有Cell尺寸 collectionView.collectionViewLayout.invalidateLayout() } // 记得移除监听,避免内存泄漏 deinit { NotificationCenter.default.removeObserver(self) }
2. 修正FlowLayout的预估尺寸,消除布局歧义
FlowLayout的estimatedItemSize如果设置不当,会在动态尺寸变化时出现计算错误。你可以显式设置预估尺寸和当前Cell尺寸一致,同时调整sizeForItemAt的取值逻辑:
override func viewDidLoad() { super.viewDidLoad() guard let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return } // 设置预估尺寸为当前CollectionView的实际尺寸 flowLayout.estimatedItemSize = collectionView.frame.size } // 改用frame.size而非bounds,避免滚动时bounds变化影响计算 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return collectionView.frame.size }
3. 配合拖拽关闭状态,临时禁用滚动
在拖拽关闭控制器的过程中,CollectionView的滚动可能会加剧尺寸冲突。你可以通过PresentationController的代理,在拖拽开始时禁用滚动,结束后恢复:
首先遵守UIAdaptivePresentationControllerDelegate协议:
class YourViewController: UIViewController, UIAdaptivePresentationControllerDelegate { // ... 其他代码 }
然后设置代理并实现方法:
override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) presentationController?.delegate = self } // 当开始拖拽关闭时,禁用滚动 func presentationControllerWillDismiss(_ presentationController: UIPresentationController) { collectionView.isScrollEnabled = false } // 关闭完成后恢复滚动 func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { collectionView.isScrollEnabled = true } // 如果拖拽被取消,也要恢复滚动 func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) { collectionView.isScrollEnabled = true }
4. 修正Cell内UIImageView的约束
你之前尝试过调整约束,但可能没注意到:Cell的内容容器是contentView而非Cell本身。确保UIImageView的约束和contentView对齐:
在Cell的初始化代码里:
imageView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), imageView.topAnchor.constraint(equalTo: contentView.topAnchor), imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) ])
这样当Cell尺寸变化时,UIImageView会正确跟随contentView调整,不会出现变形。
为什么之前的方案没生效?
- 全屏展示模态:虽然减少了尺寸变化,但拖拽关闭时仍会有动态调整,无法从根源解决冲突
- 调整content inset为
.never:只是关闭了自动调整,但没解决布局计算的根本矛盾 - 移除弹性效果:能避免越界滚动,但拖拽时的尺寸变化还是会触发布局错误
- 移除UIImageView:Cell没有子视图,布局计算不会有歧义,但不符合设计需求
按照上面的步骤试下来,应该能彻底解决你的问题。我当时就是用方案1+方案3解决的,亲测有效!
内容的提问来源于stack exchange,提问作者Jacobo Koenig

