Swift 4分页模式下如何垂直居中UICollectionView单元格?
Hey there! Let's work through the vertical centering issue you're hitting with UICollectionView. I see you started trying to adjust section insets, but let's refine that code to avoid crashes and ensure it works reliably.
First, Let's Fix Your Inset Calculation Code
Your initial approach is on the right track, but the forced unwraps (!) are risky—they'll crash if any of those optional values are nil. Plus, we need to handle cases where content is taller than the collection view (no need to center then). Here's a safe, working version:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { // Safely get navigation bar height (default to 0 if nil) let navBarHeight = navigationController?.navigationBar.frame.height ?? 0 // Guard against nil collection view height guard let collectionViewHeight = collectionView.frame.height else { return .zero } let availableHeight = collectionViewHeight - navBarHeight // Guard against nil content size height guard let contentHeight = collectionView.contentSize.height else { return .zero } // Only apply top inset if content is shorter than available space let topInset = max(0, (availableHeight - contentHeight) / 2) return UIEdgeInsets(top: topInset, left: 0, bottom: 0, right: 0) }
Critical Note: Ensure Layout Updates Trigger Correctly
A common pitfall here is that collectionView.contentSize might not be up-to-date when insetForSectionAt is called. To fix this, force a layout invalidation whenever the view's layout changes. Add this to your view controller:
override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() collectionView?.collectionViewLayout.invalidateLayout() }
This tells the collection view to recalculate insets every time the view resizes (like when rotating the device or navigating back).
Alternative: Custom Flow Layout for More Reliability
If you want even more control (or if the inset method feels flaky), create a custom UICollectionViewFlowLayout subclass. This gives you direct access to layout attributes and avoids relying on content size timing issues:
class CenteredVerticalFlowLayout: UICollectionViewFlowLayout { override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard let baseAttributes = super.layoutAttributesForElements(in: rect), let collectionView = collectionView else { return super.layoutAttributesForElements(in: rect) } let navBarHeight = collectionView.navigationController?.navigationBar.frame.height ?? 0 let availableHeight = collectionView.frame.height - navBarHeight let contentHeight = collectionView.contentSize.height // Only adjust layout if content fits in available space guard contentHeight < availableHeight else { return baseAttributes } let verticalOffset = (availableHeight - contentHeight) / 2 // Apply offset to all cell attributes return baseAttributes.map { attributes in let adjustedAttributes = attributes.copy() as! UICollectionViewLayoutAttributes if adjustedAttributes.representedElementCategory == .cell { adjustedAttributes.frame = adjustedAttributes.frame.offsetBy(dx: 0, dy: verticalOffset) } return adjustedAttributes } } }
To use this, set it as your collection view's layout either in code:
let layout = CenteredVerticalFlowLayout() let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
Or in Interface Builder: Select your collection view's layout, go to the Identity Inspector, and set the class to CenteredVerticalFlowLayout.
Key Takeaways
- Avoid forced unwraps (
!) at all costs—use optional binding (guard let) or default values (??) instead. - Always trigger layout invalidation when the view's bounds change to keep insets accurate.
- Custom flow layouts are great for more complex centering needs or when content size timing is an issue.
内容的提问来源于stack exchange,提问作者Saeed Rahmatolahi

