UICollectionView自定义布局问题:大小单元格自动排版异常
Fixing Mixed Cell Size Layout Glitches in Custom UICollectionViewLayout
Got it, let's tackle this layout glitch you're seeing. The core issue is how your prepare() method tracks xOffset, column, and yOffset—right now, it assumes small cells always come in pairs, so when you have a single small cell (or a large cell followed by a small one), the positioning variables get out of sync, leading to misaligned cells.
Key Problems in Your Current Code
- The
columntoggle logic (column = column < 1 ? column + 1 : 0) doesn't reset properly after large cells, which should always start a new row. - The
yOffsetcalculation relies on checking the next cell's size, which isn't a reliable way to decide when to move to a new row. xOffsetfor small cells doesn't correctly reset after large cells, causing them to appear in the wrong horizontal position.
Corrected Implementation
Here's a revised version of your FlexLayout with fixed logic that handles all combinations of large/small cells:
import UIKit protocol FlexLayoutDelegate: class { func collectionView(_ collectionView: UICollectionView, sizeForViewAtIndexPath indexPath: IndexPath) -> Int } class FlexLayout: UICollectionViewLayout { weak var delegate: FlexLayoutDelegate! fileprivate var cellPadding: CGFloat = 10 fileprivate var cache = [UICollectionViewLayoutAttributes]() fileprivate var contentHeight: CGFloat = 0 fileprivate var contentWidth: CGFloat { guard let collectionView = collectionView else { return 0 } let insets = collectionView.contentInset return collectionView.bounds.width - (insets.left + insets.right) } override var collectionViewContentSize: CGSize { return CGSize(width: contentWidth, height: contentHeight) } override func prepare() { guard cache.isEmpty, let collectionView = collectionView else { return } var currentY: CGFloat = 0 var currentX: CGFloat = 0 let smallCellWidth = contentWidth / 2 let largeCellWidth = contentWidth let cellHeight = cellPadding * 2 + 240 for item in 0 ..< collectionView.numberOfItems(inSection: 0) { let indexPath = IndexPath(item: item, section: 0) let viewSize = delegate.collectionView(collectionView, sizeForViewAtIndexPath: indexPath) var cellWidth: CGFloat switch viewSize { case 1: // Full-width large cell cellWidth = largeCellWidth // Large cell starts a new row currentX = 0 case 2: // Half-width small cell cellWidth = smallCellWidth // Check if this small cell fits in the current row if currentX + cellWidth > contentWidth { // No space, move to next row currentY += cellHeight currentX = 0 } default: cellWidth = smallCellWidth // Fallback to small cell if size is invalid } // Build cell frame (with padding) let frame = CGRect(x: currentX, y: currentY, width: cellWidth, height: cellHeight) let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding) let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath) attributes.frame = insetFrame cache.append(attributes) // Update total content height contentHeight = max(contentHeight, frame.maxY) // Adjust offsets for next cell if viewSize == 1 { // After large cell, move to next row currentY += cellHeight currentX = 0 } else { // Shift X for the next small cell in the row currentX += cellWidth } } } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { return cache.filter { $0.frame.intersects(rect) } } override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { return cache[indexPath.item] } }
What's Improved
- Simplified Offset Tracking: Ditched the
columnvariable for explicitcurrentXandcurrentYoffsets, making row/column logic easier to follow. - Clear Row Break Rules:
- Large cells always take the full row, resetting
currentXand movingcurrentYdown after placement. - Small cells check if they fit in the current row—if not, they start a new row.
- Large cells always take the full row, resetting
- Removed Unreliable Next Cell Check: We no longer depend on the next cell's size to decide row breaks, which fixes cases where a small cell follows a large cell.
- Predefined Cell Dimensions: Calculated
smallCellWidthandlargeCellWidthonce at the start to avoid redundant math.
Optional: Center Single Small Cells
If you want single small cells to be centered instead of left-aligned, add this check right before setting the frame for small cells:
if viewSize == 2 { // Check if this is the last cell, or the next cell is large let isLastCell = item == collectionView.numberOfItems(inSection: 0) - 1 let nextIsLarge = !isLastCell && delegate.collectionView(collectionView, sizeForViewAtIndexPath: IndexPath(item: item+1, section: 0)) == 1 if isLastCell || nextIsLarge { // Center the single small cell currentX = (contentWidth - cellWidth) / 2 } }
内容的提问来源于stack exchange,提问作者user3391232
相关产品推荐
相关产品推荐

