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

Swift中使用IGListKit创建双列Collection View列表的问题

解决IGListKit双列Collection View显示为单列的问题

看起来你遇到的核心问题是每个Channel对象对应了一个独立的ListSectionController,而UICollectionView的FlowLayout默认会把每个section的内容作为独立的垂直块排列——哪怕单个item的宽度很小,每个section的item也会占据单独一行,最终呈现出单列的效果。

下面给你两种可行的解决方案:

方案一:将所有Channel放入同一个SectionController(推荐)

这种方式更符合IGListKit的设计逻辑,把同类型的多个item归到一个section中:

1. 创建一个Section模型类

用来包裹所有Channel,作为SectionController的数据源:

struct ChannelSectionModel: ListDiffable {
    let channels: [Channel]
    private let sectionId = "channel_group"
    
    func diffIdentifier() -> NSObjectProtocol {
        return sectionId as NSObjectProtocol
    }
    
    func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
        guard let other = object as? ChannelSectionModel else { return false }
        // 这里需要Channel实现Equatable,或者对比所有channel的id
        return channels.map(\.id) == other.channels.map(\.id)
    }
}

2. 修改ViewController的数据源逻辑

返回包含所有Channel的单个section模型:

class ViewController: UIViewController, ListAdapterDataSource {
    lazy var adapter: ListAdapter = {
        return ListAdapter(updater: ListAdapterUpdater(), viewController: self)
    }()
    @IBOutlet weak var collectionView: UICollectionView!
    
    // 用SectionModel包裹所有Channel
    let channelSection = ChannelSectionModel(channels: [
        Channel(id: "1", color: UIColor.black),
        Channel(id: "2", color: UIColor.blue),
        Channel(id: "3", color: UIColor.red),
        Channel(id: "4", color: UIColor.yellow),
        Channel(id: "5", color: UIColor.green),
    ])
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.adapter.collectionView = self.collectionView
        self.adapter.dataSource = self
        
        // 配置FlowLayout的间距,确保双列布局美观
        if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
            flowLayout.minimumInteritemSpacing = 8
            flowLayout.minimumLineSpacing = 8
            flowLayout.sectionInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
        }
    }
    
    func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
        return [channelSection] // 返回单个section模型
    }
    
    func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
        return MultiChannelSectionController()
    }
    
    func emptyView(for listAdapter: ListAdapter) -> UIView? {
        return nil
    }
}

3. 实现支持多item的SectionController

final class MultiChannelSectionController: ListSectionController {
    var sectionModel: ChannelSectionModel?
    
    override func numberOfItems() -> Int {
        return sectionModel?.channels.count ?? 0
    }
    
    override func sizeForItem(at index: Int) -> CGSize {
        guard let containerWidth = collectionContext?.containerSize.width else { return .zero }
        // 计算双列宽度:减去左右内边距和item间距
        let availableWidth = containerWidth - 8*2 - 8 // 左右各8内边距,中间8间距
        let itemWidth = availableWidth / 2
        return CGSize(width: itemWidth, height: itemWidth)
    }
    
    override func cellForItem(at index: Int) -> UICollectionViewCell {
        guard let sectionModel = sectionModel, index < sectionModel.channels.count else { fatalError() }
        let channel = sectionModel.channels[index]
        guard let cell = collectionContext?.dequeueReusableCellFromStoryboard(withIdentifier: "ChannelCell", for: self, at: index) as? ChannelCollectionViewCell else { fatalError() }
        cell.channel = channel
        return cell
    }
    
    override func didUpdate(to object: Any) {
        sectionModel = object as? ChannelSectionModel
    }
}

4. 确保Channel实现完整的ListDiffable协议

你的原代码中强制转换channels as! [ListDiffable],需要Channel正确实现协议方法:

class Channel: ListDiffable {
    let id: String
    let color: UIColor
    
    init(id: String, color: UIColor) {
        self.id = id
        self.color = color
    }
    
    func diffIdentifier() -> NSObjectProtocol {
        return id as NSObjectProtocol
    }
    
    func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
        guard let other = object as? Channel else { return false }
        return id == other.id && color == other.color
    }
}

方案二:保持单个Channel对应SectionController,修改Layout配置(不推荐)

如果一定要保持每个Channel单独一个section,可以通过修改FlowLayout的sectionInsetReference和强制让section横向排列,但这种方式会破坏IGListKit的section语义,不建议使用:

override func viewDidLoad() {
    super.viewDidLoad()
    // ...其他代码
    if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
        flowLayout.scrollDirection = .horizontal
        flowLayout.minimumLineSpacing = 8
        flowLayout.sectionInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
        // 同时修改sizeForItem的宽度为合适值
    }
}

关键注意点

  • 确认你的CollectionView在Storyboard中使用的是UICollectionViewFlowLayout(默认就是这个),如果是自定义Layout需要额外适配。
  • sizeForItem中计算宽度时,一定要考虑内边距和item间距,否则会出现换行异常。

内容的提问来源于stack exchange,提问作者ken

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:15:36