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

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 column toggle logic (column = column < 1 ? column + 1 : 0) doesn't reset properly after large cells, which should always start a new row.
  • The yOffset calculation relies on checking the next cell's size, which isn't a reliable way to decide when to move to a new row.
  • xOffset for 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

  1. Simplified Offset Tracking: Ditched the column variable for explicit currentX and currentY offsets, making row/column logic easier to follow.
  2. Clear Row Break Rules:
    • Large cells always take the full row, resetting currentX and moving currentY down after placement.
    • Small cells check if they fit in the current row—if not, they start a new row.
  3. 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.
  4. Predefined Cell Dimensions: Calculated smallCellWidth and largeCellWidth once 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:58:19