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

如何实现UICollectionView首分区居左、次分区居右的双列布局

当然有可行的实现方法!我给你两种思路,一种是用单个UICollectionView+自定义布局来实现,另一种是用两个并排的UICollectionView作为替代方案,都能满足你的需求:

方案一:自定义UICollectionViewLayout(单CollectionView实现)

这是最贴合你需求的方式,通过自定义布局来控制两个分区的位置:

  1. 核心思路:

    • 将屏幕宽度平均分成两部分,第一个分区的所有元素(包括头部)占据左半屏,第二个分区占据右半屏。
    • 重写布局类的关键方法,为每个分区的Cell和头部单独计算布局属性。
  2. 代码示例:
    首先创建自定义布局类:

    class DualColumnLayout: UICollectionViewLayout {
        private var cache = [UICollectionViewLayoutAttributes]()
        private var contentHeight: CGFloat = 0
        private let columnWidth: CGFloat
        private let spacing: CGFloat = 8
        
        override init() {
            let screenWidth = UIScreen.main.bounds.width
            columnWidth = (screenWidth - spacing) / 2
            super.init()
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        override var collectionViewContentSize: CGSize {
            return CGSize(width: UIScreen.main.bounds.width, height: contentHeight)
        }
        
        override func prepare() {
            super.prepare()
            guard let collectionView = collectionView else { return }
            cache.removeAll()
            contentHeight = 0
            
            // 处理第一个分区(左列)
            let leftSection = 0
            let leftSectionInset = collectionView.insetsForSection(at: leftSection)
            var leftYOffset = leftSectionInset.top
            // 添加左分区头部
            let leftHeaderAttrs = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, with: IndexPath(item: 0, section: leftSection))
            leftHeaderAttrs.frame = CGRect(x: leftSectionInset.left, y: leftYOffset, width: columnWidth - leftSectionInset.left - leftSectionInset.right, height: 50) // 自定义头部高度
            cache.append(leftHeaderAttrs)
            leftYOffset += leftHeaderAttrs.frame.height + leftSectionInset.top
            
            // 布局左分区Cell
            let leftItemCount = collectionView.numberOfItems(inSection: leftSection)
            for item in 0..<leftItemCount {
                let indexPath = IndexPath(item: item, section: leftSection)
                let attrs = UICollectionViewLayoutAttributes(forCellWith: indexPath)
                attrs.frame = CGRect(x: leftSectionInset.left, y: leftYOffset, width: columnWidth - leftSectionInset.left - leftSectionInset.right, height: 100) // 自定义Cell高度
                cache.append(attrs)
                leftYOffset += attrs.frame.height + spacing
            }
            contentHeight = max(contentHeight, leftYOffset)
            
            // 处理第二个分区(右列)
            let rightSection = 1
            let rightSectionInset = collectionView.insetsForSection(at: rightSection)
            var rightYOffset = rightSectionInset.top
            // 添加右分区头部
            let rightHeaderAttrs = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, with: IndexPath(item: 0, section: rightSection))
            rightHeaderAttrs.frame = CGRect(x: columnWidth + spacing + rightSectionInset.left, y: rightYOffset, width: columnWidth - rightSectionInset.left - rightSectionInset.right, height: 50)
            cache.append(rightHeaderAttrs)
            rightYOffset += rightHeaderAttrs.frame.height + rightSectionInset.top
            
            // 布局右分区Cell
            let rightItemCount = collectionView.numberOfItems(inSection: rightSection)
            for item in 0..<rightItemCount {
                let indexPath = IndexPath(item: item, section: rightSection)
                let attrs = UICollectionViewLayoutAttributes(forCellWith: indexPath)
                attrs.frame = CGRect(x: columnWidth + spacing + rightSectionInset.left, y: rightYOffset, width: columnWidth - rightSectionInset.left - rightSectionInset.right, height: 100)
                cache.append(attrs)
                rightYOffset += attrs.frame.height + spacing
            }
            contentHeight = max(contentHeight, rightYOffset)
        }
        
        override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
            return cache.filter { $0.frame.intersects(rect) }
        }
        
        override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
            return cache.first { $0.indexPath == indexPath }
        }
        
        override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
            return cache.first { $0.indexPath == indexPath && $0.representedElementKind == elementKind }
        }
        
        override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
            return true // 适配屏幕旋转
        }
    }
    

    然后在ViewController中设置:

    class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
        private var collectionView: UICollectionView!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            let layout = DualColumnLayout()
            collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
            collectionView.dataSource = self
            collectionView.delegate = self
            collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
            collectionView.register(UICollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "Header")
            view.addSubview(collectionView)
        }
        
        // 实现数据源方法
        func numberOfSections(in collectionView: UICollectionView) -> Int {
            return 2
        }
        
        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            return section == 0 ? 5 : 7 // 示例数量
        }
        
        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
            cell.backgroundColor = indexPath.section == 0 ? .systemBlue : .systemGreen
            return cell
        }
        
        func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
            let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "Header", for: indexPath)
            header.backgroundColor = .systemGray
            // 添加标题标签等
            let label = UILabel(frame: header.bounds)
            label.text = indexPath.section == 0 ? "左分区头部" : "右分区头部"
            label.textAlignment = .center
            header.addSubview(label)
            return header
        }
    }
    
方案二:两个并排的UICollectionView(替代方案)

如果自定义布局觉得太复杂,这个方案更简单易维护:

  • 核心思路:在父视图中放置两个UICollectionView,左边的对应原第一个分区,右边的对应原第二个分区,用AutoLayout让它们各占屏幕宽度的一半(可调整间距)。
  • 优点:每个CollectionView可以直接使用系统的UICollectionViewFlowLayout,单独设置自己的分区头部,无需复杂的自定义布局代码,适配和调试更方便。

代码示例:

class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
    private var leftCollectionView: UICollectionView!
    private var rightCollectionView: UICollectionView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 创建左CollectionView
        let leftFlowLayout = UICollectionViewFlowLayout()
        leftFlowLayout.sectionHeadersPinToVisibleBounds = true // 可选:头部吸顶
        leftCollectionView = UICollectionView(frame: .zero, collectionViewLayout: leftFlowLayout)
        leftCollectionView.dataSource = self
        leftCollectionView.delegate = self
        leftCollectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "LeftCell")
        leftCollectionView.register(UICollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "LeftHeader")
        leftCollectionView.backgroundColor = .systemBackground
        view.addSubview(leftCollectionView)
        
        // 创建右CollectionView
        let rightFlowLayout = UICollectionViewFlowLayout()
        rightFlowLayout.sectionHeadersPinToVisibleBounds = true
        rightCollectionView = UICollectionView(frame: .zero, collectionViewLayout: rightFlowLayout)
        rightCollectionView.dataSource = self
        rightCollectionView.delegate = self
        rightCollectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "RightCell")
        rightCollectionView.register(UICollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "RightHeader")
        rightCollectionView.backgroundColor = .systemBackground
        view.addSubview(rightCollectionView)
        
        // 设置AutoLayout
        leftCollectionView.translatesAutoresizingMaskIntoConstraints = false
        rightCollectionView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            leftCollectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            leftCollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            leftCollectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
            leftCollectionView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5, constant: -4), // 4是间距的一半
            
            rightCollectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            rightCollectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            rightCollectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
            rightCollectionView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5, constant: -4),
            rightCollectionView.leadingAnchor.constraint(equalTo: leftCollectionView.trailingAnchor, constant: 8) // 间距8
        ])
    }
    
    // 数据源方法区分两个CollectionView
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1 // 每个CollectionView对应一个分区
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return collectionView == leftCollectionView ? 5 : 7
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: collectionView == leftCollectionView ? "LeftCell" : "RightCell", for: indexPath)
        cell.backgroundColor = collectionView == leftCollectionView ? .systemBlue : .systemGreen
        return cell
    }
    
    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: collectionView == leftCollectionView ? "LeftHeader" : "RightHeader", for: indexPath)
        header.backgroundColor = .systemGray
        let label = UILabel(frame: header.bounds)
        label.text = collectionView == leftCollectionView ? "左分区头部" : "右分区头部"
        label.textAlignment = .center
        header.addSubview(label)
        return header
    }
    
    // 设置头部高度
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
        return CGSize(width: collectionView.bounds.width, height: 50)
    }
    
    // 设置Cell大小
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: collectionView.bounds.width - 16, height: 100) // 左右边距各8
    }
}
注意事项
  • 两种方案都要考虑屏幕旋转适配:自定义布局重写shouldInvalidateLayout(forBoundsChange:)返回true;双CollectionView方案AutoLayout会自动适配。
  • 可以根据需求调整间距、头部高度、Cell高度等参数。
  • 如果需要分区头部吸顶效果,自定义布局需要额外处理,双CollectionView方案直接设置sectionHeadersPinToVisibleBounds = true即可。

内容的提问来源于stack exchange,提问作者Hari R Krishna

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:53:23