使用UICollectionViewCompositionalLayout的自适配单元格在pageSheet中的异常修复
I've run into this exact issue before with page sheets and self-sizing compositional layout cells, and yes—you absolutely can fix this while keeping both the UICollectionViewCompositionalLayout and page sheet presentation style. The root problem is that when you drag the page sheet to resize it, the collection view's cached layout calculations don't update properly for self-sizing cells. Here's a step-by-step solution:
1. Fix the Cell Layout with Proper Auto Layout
You mentioned trying Auto Layout instead of sizeThatFits(_:), but it's likely your constraints weren't set up to fully drive the cell's size. Let's rewrite the cell to use explicit constraints that tie directly to its content:
final class Cell: UICollectionViewCell { static let reuseIdentifier = "Cell" lazy var label: UILabel = { let label = UILabel() label.textAlignment = .center label.translatesAutoresizingMaskIntoConstraints = false return label }() override init(frame: CGRect) { super.init(frame: frame) contentView.addSubview(label) contentView.backgroundColor = .tertiarySystemFill // Pin the label with padding, and fix the cell height NSLayoutConstraint.activate([ label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16), label.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), label.heightAnchor.constraint(equalToConstant: 32), contentView.heightAnchor.constraint(equalToConstant: 32) ]) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
This setup ensures the cell's width is determined entirely by the label's content (plus padding), and the height stays fixed as intended. We can ditch sizeThatFits(_:) entirely now—Auto Layout handles the sizing.
2. Listen for Page Sheet Size Changes to Refresh Layout
When you drag the page sheet, the view controller's bounds change, but the collection view doesn't always invalidate its layout automatically. We'll add an observer to force a layout refresh when this happens:
final class BugViewController: UIViewController { // ... (your existing properties and setup) ... override func viewDidLoad() { super.viewDidLoad() view.addSubview(collectionView) // Add observer for bounds changes (triggered when page sheet is resized) view.addObserver(self, forKeyPath: "bounds", options: .new, context: nil) // Apply your snapshot as before var snapshot = NSDiffableDataSourceSnapshot<Int, String>() snapshot.appendSections([0]) snapshot.appendItems(models) dataSource.apply(snapshot) } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { guard keyPath == "bounds" else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) return } // Invalidate the layout to recalculate cell sizes, then refresh visible cells collectionView.collectionViewLayout.invalidateLayout() collectionView.reloadItems(at: collectionView.indexPathsForVisibleItems) } deinit { // Clean up the observer to avoid memory leaks view.removeObserver(self, forKeyPath: "bounds") } // ... (your existing methods) ... }
This ensures every time the page sheet's size changes, the collection view discards its cached layout and recalculates all cell sizes from scratch.
3. Optimize the Compositional Layout Configuration
Tweak the layout to be more flexible with estimated sizes and explicit spacing, which helps avoid layout shifts and incorrect offsets:
private func createCollectionViewLayout() -> UICollectionViewCompositionalLayout { // Item size: estimated width (smaller initial value for better flexibility) + fixed height let itemSize = NSCollectionLayoutSize( widthDimension: .estimated(100), heightDimension: .absolute(32) ) let item = NSCollectionLayoutItem(layoutSize: itemSize) // Group size matches item size (since it's a horizontal group) let groupSize = NSCollectionLayoutSize( widthDimension: .estimated(100), heightDimension: .absolute(32) ) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) group.interItemSpacing = .fixed(8) // Explicit spacing between cells let section = NSCollectionLayoutSection(group: group) section.orthogonalScrollingBehavior = .continuous section.contentInsets = NSDirectionalEdgeInsets(top: 44, leading: 16, bottom: 0, trailing: 16) // Move top inset to section instead of collection view return UICollectionViewCompositionalLayout(section: section) }
Note that we moved the top content inset from the collection view to the section—this keeps the layout logic centralized and avoids conflicts with the collection view's own insets.
Why This Works
- Auto Layout: Eliminates the ambiguity of
sizeThatFits(_:), ensuring the cell's size is always tied directly to its content. - Bounds Observer: Forces the collection view to recalculate layout every time the page sheet resizes, preventing cached incorrect sizes.
- Layout Tweaks: Using a smaller estimated width gives the layout system more room to adjust, and explicit spacing/insets prevent unexpected offsets.
With these changes, your self-sizing cells will behave correctly even when dragging the page sheet—no more missing cells, wrong sizes, or horizontal shifts.
内容的提问来源于stack exchange,提问作者JWK

