具有多个部分的 UICollectionView

Posted

技术标签:

【中文标题】具有多个部分的 UICollectionView【英文标题】:UICollectionView with multiple sections 【发布时间】:2021-02-03 10:15:31 【问题描述】:

大家好,我需要在 collectionView 中水平滚动显示 2 个部分。

第一部分需要 10 个单元格,第二部分需要 9 个单元格

我已经实现了这种方式,但我不明白为什么当我选择第 2 节的单元格时,indexPath.item 的值是第 1 节的单元格的值

编辑

你看到我的collectionView有2个部分设置单元格像这样..我不需要显示这样的单元格,但我需要第1部分在第一行,它必须显示像这样的项目同一行 (0,1 2 3 4 5) 和底行的第 2 部分,并显示顶行的项目

我哪里做错了?

private func commonInit() -> Void 
        backgroundColor = .clear
        
        let cv = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
        cv.backgroundColor = .clear
        cv.delegate = self
        cv.dataSource = self
        cv.isPagingEnabled = true
        cv.showsHorizontalScrollIndicator = false
        addSubview(cv)
            
        if let layout = cv.collectionViewLayout as? UICollectionViewFlowLayout 
            layout.scrollDirection = .horizontal 
        
        TimeSelectorCell.register(in: cv)
        
        cv.anchor(top: topAnchor, leading: leadingAnchor, bottom: bottomAnchor, trailing: trailingAnchor)

    

    func numberOfSections(in collectionView: UICollectionView) -> Int  2 
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 
        if section == 0  return 10 
        else  return 9 
    
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
        
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TimeSelectorCell.cellIdentifier, for: indexPath) as! TimeSelectorCell
    
        
        return cell
    
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) 
        print(indexPath.item)
    

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize 
        .init(width: collectionView.frame.width / 4 , height: collectionView.frame.height / 2)
    
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat  0 
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat  0 

【问题讨论】:

在 'didSelectItemAt' 中,您可以使用 "indexPath.section" 来检查它是哪个部分。只是为了确认这是否真的是问题。 @rana5ohaib 我试过了,它总是返回 indexpath.section 0 直到单元格 10 .. 问题是第 0 部分似乎被分成两行,然后第 1 部分开始.. 而我需要部分0 位于顶部,第 1 部分位于第 0 部分的下方 你需要实现一个自定义的 UICollectionViewLayout。对于水平流布局,它将首先从上到下填充,然后向右移动。由于您有两行,如 sizeForItemAt 中指定的那样,第 0 部分将从上到下填充,然后从右到左填充,第 1 部分也是如此。 你能创建一个示例项目吗? @kAiN - 你想要第一个“部分”在“第一行”上包含单元格 0 到 9,第二部分在“第二行”上包含单元格 0 到 8?您希望各行彼此独立滚动吗? 【参考方案1】:

在这些场景中,你必须使用的是 ios 13 在 CollectionView 中的 Compositional Layouts。这里有2个教程供你理解-

    https://www.zealousweb.com/how-to-use-compositional-layout-in-collection-view/ https://medium.com/better-programming/ios-13-compositional-layouts-in-collectionview-90a574b410b8 WWDC 视频 - https://developer.apple.com/videos/play/wwdc2019/215/

对于您的情况,我已经调整了第二个教程中的示例

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 
    return (section == 0) ? 10 : 9



func numberOfSections(in collectionView: UICollectionView) -> Int 
    return 2


func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) 
    print(indexPath)


func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
        
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TimeSelectorCell.cellIdentifier, for: indexPath) as! TimeSelectorCell
    
    return cell


private func commonInit() 
    let cv = UICollectionView(frame: view.bounds, collectionViewLayout: createLayoutDiffSection())
    cv.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    TimeSelectorCell.register(in: cv)
    
    cv.dataSource = self
    cv.delegate = self
    
    addSubview(cv)

到目前为止,它与您的代码几乎相同。现在 createLayoutDiffSection() 方法是 -

func createLayoutDiffSection() -> UICollectionViewLayout 
    
    let layout = UICollectionViewCompositionalLayout  (sectionIndex: Int,
        layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
        
        let seperateFlow = true
        
        let columns = (sectionIndex == 0) ? 10 : 9
        
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                              heightDimension: .fractionalHeight(1.0))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 2, bottom: 0, trailing: 2)
        
        let groupHeight = NSCollectionLayoutDimension.fractionalWidth(0.2)

let widthFraction:CGFloat = (sectionIndex == 0) ? 1 : 0.9
        
        let groupSizeWidth:CGFloat = seperateFlow ? (widthFraction * 2) : 1
        
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(groupSizeWidth),
                                               heightDimension: groupHeight)
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: columns)
        
        let section = NSCollectionLayoutSection(group: group)
        if seperateFlow 
            section.orthogonalScrollingBehavior = .continuous
        
        section.contentInsets = NSDirectionalEdgeInsets(top: 20, leading: 0, bottom: 20, trailing: 0)
        return section
    
    return layout

如果两个部分的滚动不是分开的,则设置 -

让seperateFlow = false

这里要理解的重点是-

大小类

我们可以根据需要使用四种尺寸选项,这样就无需进行计算即可获得所需的输出。让我们看看所有的尺寸等级:

    fractionalWidth():使用fractionalWidth我们可以设置宽度/高度 单元格的宽度与其父级的宽度成正比 fractionalHeight(): 使用fractionalHeight,我们可以设置单元格的宽度/高度 与其父母的身高成正比 absolute():使用绝对 值,我们可以将单元格的宽度/高度设置为固定值 estimate():使用估计值,我们可以设置单元格的宽/高 根据内容大小。系统将决定最优 内容的宽度/高度。

核心概念

要构建任何组合布局,需要实现以下四个类:

    NSCollectionLayoutSize — 宽度和高度尺寸是 类型 NSCollectionLayoutDimension 可以通过设置 布局的小数宽度/高度(相对于其的百分比 容器),或通过设置绝对或估计大小。 NSCollectionLayoutItem — 这是您的布局单元格,在 屏幕大小。 NSCollectionLayoutGroup — 它以水平、垂直或自定义形式保存 NSCollectionLayoutItem。 NSCollectionLayoutSection - 这用于通过传递 NSCollectionLayoutGroup 来初始化部分。部分最终构成了组合布局。

【讨论】:

在此感谢您的建议。。虽然对我来说是第一次使用CompositionalLayout,但它非常完整,实际上我仍然无法理解它是如何工作的。无论如何,我尝试更改一些值,它似乎工作正常,所以谢谢。我只有一个问题.. 我注意到要使第 1 部分和第 2 部分的单元格尺寸都相同,两个部分的单元格数量必须相同,因为显然单元格的宽度是 1 的分数两个部分。 即使单元格的数量不同,我如何才能在两个部分中拥有相同的单元格大小?是否需要在包含 9 而不是 10 的部分中添加幽灵单元格? 我已经修改了代码。我添加了 widthFraction 常量。并修改了 groupSizeWidth 的代码。是的,组合布局一开始就很难掌握。请使用 WWDC 视频和我分享的第一个教程。这些都很好。 您好,再次感谢您的帮助.. 我已经实现了您的更改,但现在如果我需要滚动两个部分而不是单独滚动,设置 let separatorFlow = false 我的单元格会丢失所有尺寸和他们有如果我设置 let separatorFlow = true【参考方案2】:
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)  
     
    if (indexPath.section == 0 )
    
      // cell of section 1
       print(indexPath.item)
    else
     // cell of section 2
      print(indexPath.item)
     
     
   

你可以试试!

【讨论】:

以上是关于具有多个部分的 UICollectionView的主要内容,如果未能解决你的问题,请参考以下文章

集合视图具有多个部分的不同单元格高度

具有多个部分的表格视图

具有部分和多个项目的 IGListKit

具有多个部分的静态分组 UITableView 的默认外观

快速具有多个部分和多行的表格视图

如何使用 RxDatasource 在 UICollectionView 中创建具有多个标题的多个部分