UICollectionView 多个部分和标题

Posted

技术标签:

【中文标题】UICollectionView 多个部分和标题【英文标题】:UICollectionView Multiple Sections and Headers 【发布时间】:2015-01-23 03:39:43 【问题描述】:

我正在努力尝试在我的集合视图中创建多个部分,每个部分都有一个标题。我不知道 Obj-C,我已经找到了大量的教程,但还没有弄清楚如何将它转换成 Swift。

我所有的数据都是静态的,所以我需要的只是某种数组或字典,我可以使用它们来创建多个部分。我已经有一个包含 1 个部分的集合视图,因此,如果您对多个部分有任何见解或代码会有所帮助。

我知道如何使用

设置多个部分
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int   
    return sectionData.count

我认为我需要帮助的主要是实现这个函数

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell  

并设置数据!

UICollectionView 和 UITableView 几乎完全一样,所以如果你知道如何在 Swift 中的 UITableView 中做多个部分,也感谢你的帮助

【问题讨论】:

我是这样做的:github.com/mattneub/Programming-ios-Book-Examples/blob/master/… 这是一个可下载项目的一部分,因此您可以下载并运行它。模型数据结构配置在viewDidLoad。在数据源方法中从中检索正确的数据非常容易。 根据你的教程想出来的! 太好了,谢谢你告诉我! 【参考方案1】:

cellForItemAtIndexPath 函数处理用单元格填充每个部分,它不处理部分或补充视图,因此在创建部分标题时不是您需要帮助的主要内容。

你需要实现的方法是viewForSupplementaryElementOfKind。它的签名是:

func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView 

假设您的 collectionView 对 1 个部分正常工作(您已正确填写了 cellForItemAtIndexPath 的正文,并且您的 sectionData 数组正确反映了您要显示的部分数量),您应该能够使用以下指针:

除了单元格,UICollectionView 还支持“补充”视图对象,通常用于页眉或页脚。这些补充视图的行为与UICollectionViewCell 对象非常相似。与cellForItemAtIndexPath 处理单元格的方式相同,viewForSupplementaryElementOfKind 函数处理补充视图。

要实现它,您需要首先准备好您的 ViewController。首先编辑您的布局对象以反映适当的标题大小,每个标题都将遵守:

 let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
 layout.headerReferenceSize = CGSize(width: self.view.frame.size.width, height: 30)

注意:我使用的是 UICollectionViewFlowLayout

接下来,如果您还没有这样做,请创建一个定义每个节标题对象的 SectionHeader 类,这样您就可以使用您的 collectionView 对象注册该类,如下所示:

  collectionView!.registerClass(SectionHeaderView.self, forSupplementaryViewOfKind:UICollectionElementKindSectionHeader, withReuseIdentifier: "SectionHeaderView");

这里传入的第一个和第三个参数同一个UICollectionViewCell类注册,这个方法中的第一个参数是对你创建的节头类的引用。第三个是补充视图的重用标识符。

第二个参数特定于Supplementary Views,它设置了SupplementaryView的kind,在这种情况下是一个header,由UICollectionViewFlowLayout类UICollectionElementKindSectionHeader提供的常量字符串用于它.如果您注意到viewForSupplementaryElementOfKind 上的参数,这个kind 稍后会作为kind: String 参数传入。

填写 viewForSupplementaryElementOfKind 的正文,方法与填写 cellForItemAtIndexPath 函数相同——使用 dequeueReusableSupplementaryViewOfKind 方法创建 SectionHeader 对象,然后根据需要设置任何属性(标签、颜色等)和最后返回头对象。

希望这会有所帮助!

参考点:

https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UICollectionViewDataSource_protocol/index.html#//apple_ref/occ/intfm/UICollectionViewDataSource/

https://developer.apple.com/library/ios/documentation/UIKit/Reference/UICollectionViewFlowLayout_class/index.html#//apple_ref/c/data/UICollectionElementKindSectionHeade

【讨论】:

它按预期工作。唯一的问题是 Class 部分必须是 ReusableView 的子类。 解释得很好 @mkncode 你能解释一下节头类的具体内容吗?【参考方案2】:

定义你的 UICollectionViewCell 这将是你的类型 UICollectionElementKindSectionHeader 的标题视图 - 在我的例子中,我有两个标题 - OfferHeaderCell 和 APRHeaderCell 定义如下:

verticalCollectionView.register(UINib(nibName: "OfferHeaderCell", bundle: nil), forSupplementaryViewOfKind:UICollectionElementKindSectionHeader, withReuseIdentifier: "OfferHeaderCell")
verticalCollectionView.register(UINib(nibName: "APRHeaderCell", bundle: nil), forSupplementaryViewOfKind:UICollectionElementKindSectionHeader, withReuseIdentifier: "APRHeaderCell")

继续为每个部分返回一个标题,然后在此 UICollectionViewDelegateFlowLayout 函数中将部分标题的大小设置为零

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize 
    if(section==0) 
        return CGSize.zero
     else if (section==1) 
        return CGSize(width:collectionView.frame.size.width, height:133)
     else 
        return CGSize(width:collectionView.frame.size.width, height:100)
    


为以下两个不同部分定义 viewForSupplementaryElementOfKind 很重要:

func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView 

    var reusableview = UICollectionReusableView()
    if (kind == UICollectionElementKindSectionHeader) 
        let section = indexPath.section
        switch (section) 
        case 1:
            let  firstheader: OfferHeaderCell = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "OfferHeaderCell", for: indexPath) as! OfferHeaderCell
            reusableview = firstheader
        case 2:
            let  secondHeader: APRHeaderCell = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "APRHeaderCell", for: indexPath) as! APRHeaderCell
            reusableview = secondHeader
        default:
            return reusableview

        
    
    return reusableview

最后是数据源,

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


func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 
    if (section==2) 
        return 2
    
    return 0


func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
    let cell = verticalCollectionView.dequeueReusableCell(withReuseIdentifier: "ReviseOfferCell", for: indexPath)
    cell.backgroundColor = UIColor.white
    return cell

注意:不要忘记添加 UICollectionFlowLayout 如下:

// MARK: UICollectionViewDelegateFlowLayout

extension MakeAnOfferController: UICollectionViewDelegateFlowLayout 

        func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize 

            if indexPath.item == 0 
                return CGSize(width: self.view.frame.size.width, height: 626.0)
            
            return CGSize()
        

        func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize 

            if(section==0) 
                return CGSize.zero
             else if (section==1) 
                return CGSize(width:collectionView.frame.size.width, height:133)
             else 
                return CGSize(width:collectionView.frame.size.width, height:100)
            
        
    

【讨论】:

【参考方案3】:

这是对我有用的代码

创建标题单元格。为此,我创建了一个自定义单元格类和一个 nib 以在图形编辑器中自定义单元格

在 viewDidLoad 中添加以下内容

self.collectionView?.registerNib(UINib(nibName: "KlosetCollectionHeaderViewCell", bundle: nil), forSupplementaryViewOfKind:UICollectionElementKindSectionHeader, withReuseIdentifier: "HeaderCell")

然后你添加委托函数

override func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> KlosetCollectionHeaderViewCell 

    let headerCell = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "HeaderCell", forIndexPath: indexPath) as? KlosetCollectionHeaderViewCell    

    return headerCell!
  

这会将 HeaderCell 放在 PFCollectionView 的 SectionView 中 您将它们添加到 xib 文件的单元格中显示的控件以及出口和操作

【讨论】:

【参考方案4】:

这是使用 SnapKit 以编程方式实现 UICollection 多个部分的代码

视图控制器

import SnapKit
import UIKit

class SelectIconViewController: GenericViewController<SelectIconView>, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout 

    weak var delegate: SpaceAddViewController?
    struct Section 
        var sectionName : String
        var rowData : [String]
    
    var sections : [Section]!

    init(delegate: SpaceAddViewController) 
        self.delegate = delegate
        super.init()
    


    public required init?(coder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    


    override func viewDidLoad() 
        super.viewDidLoad()

        contentView.closeButton.addTarget(self, action: #selector(self.back), for: .touchUpInside)

        self.sections = [
            Section(sectionName: "SPACES", rowData: ["Air Conditioner", "Apple HomePod"]),
            Section(sectionName: "HOME APPLIANCES", rowData: ["Ceiling Fan", "Fan", "Desk Lamp", "Iron", "PC on Desk", "Plug", "Power Strip", "Lorem", "Lorem", "Lorem", "Lorem"]),
        ]
        self.contentView.collectionView.dataSource = self
        self.contentView.collectionView.delegate = self
        self.contentView.collectionView.register(SelectIconHeaderViewCell.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: SelectIconHeaderViewCell.reuseId)
        self.contentView.collectionView.register(SelectIconViewCell.self, forCellWithReuseIdentifier: SelectIconViewCell.reuseId)

    


    @objc func back() 
        self.dismiss(animated: true, completion: nil)
    


    @objc func dismissKeyboard() 
        view.endEditing(true)
    


    func numberOfSections(in collectionView: UICollectionView) -> Int 
        return self.sections.count
    


    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 
        return self.sections[section].rowData.count
    


    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets 
        return UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
    


    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize 

        return CGSize(width: getTotalSpacing(), height: getTotalSpacing())
    


    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize 
        let screenSize = UIScreen.main.bounds
        let screenWidth = screenSize.width-40
        return CGSize(width: screenWidth-80, height: 50)
    


    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat 
        return 0
    

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat 
        return 0
    


    // MARK: Cells

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 

        let cell = self.contentView.collectionView.dequeueReusableCell(withReuseIdentifier: SelectIconViewCell.reuseId, for: indexPath as IndexPath) as! SelectIconViewCell
        cell.initializeUI()
        cell.createConstraints()
        cell.setValues(iconName: "", label: self.sections[indexPath.section].rowData[indexPath.row])
        return cell
    


    // MARK: Header
    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView 

        switch kind 
        case UICollectionView.elementKindSectionHeader:

            let cell = self.contentView.collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: SelectIconHeaderViewCell.reuseId, for: indexPath) as! SelectIconHeaderViewCell
            cell.initializeUI()
            cell.createConstraints()
            cell.setTitle(title: self.sections[indexPath.section].sectionName)
            return cell
        default:  fatalError("Unexpected element kind")
        
    


    func getTotalSpacing() -> CGFloat 

        let screenSize = UIScreen.main.bounds
        let screenWidth = screenSize.width
        let numberOfItemsPerRow:CGFloat = 3
        let spacingBetweenCells:CGFloat = 0
        let sideSpacing:CGFloat = 20
        return (screenWidth-(2 * sideSpacing) - ((numberOfItemsPerRow - 1) * spacingBetweenCells))/numberOfItemsPerRow
    


观点:

import UIKit
import SnapKit


class SelectIconView: GenericView 

    private let contentView = UIView(frame: .zero)
    private (set) var closeButton = UIButton(type: .system)
    internal var collectionView: UICollectionView!


    internal override func initializeUI() 

        self.backgroundColor = Theme.Color.white
        self.addSubview(contentView)

        contentView.addSubview(closeButton)
        if let image = UIImage(named: "icon_close") 
            image.withRenderingMode(.alwaysTemplate)
            closeButton.setImage(image, for: .normal)
            closeButton.tintColor = Theme.Color.text
        

        let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
        layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
        layout.minimumInteritemSpacing = 0
        collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
        contentView.addSubview(collectionView)
        collectionView.backgroundColor = Theme.Color.background
    


    internal override func createConstraints() 

        contentView.snp.makeConstraints  (make) in
            make.top.equalTo(safeAreaLayoutGuide.snp.top).priority(750)
            make.left.right.equalTo(self).priority(1000)
            make.bottom.equalTo(safeAreaLayoutGuide.snp.bottom)
        

        closeButton.snp.makeConstraints  make in
            make.right.equalTo(safeAreaLayoutGuide.snp.right).offset(-10)
            make.top.equalTo(contentView.snp.top).offset(10)
            make.height.equalTo(40)
            make.width.equalTo(40)
        

        collectionView.snp.makeConstraints  make in
            make.top.equalTo(closeButton.snp.bottom).offset(20)
            make.left.equalTo(safeAreaLayoutGuide.snp.left)
            make.right.equalTo(safeAreaLayoutGuide.snp.right)
            make.bottom.equalTo(contentView.snp.bottom)
        

    


自定义的section Header

import UIKit

class SelectIconHeaderViewCell: UICollectionViewCell 

    internal let mainView = UIView()
    internal var title = UILabel()

    override init(frame: CGRect) 
        super.init(frame: frame)
    

    required init?(coder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    


    func initializeUI() 

        self.backgroundColor = UIColor.clear
        self.addSubview(mainView)
        mainView.backgroundColor = UIColor.clear

        mainView.addSubview(title)
        title.text = "Pick nameA"
        title.font = Theme.Font.body()
        title.textAlignment = .left
        title.textColor = Theme.Color.text
        title.numberOfLines = 1

    


    internal func createConstraints() 

        mainView.snp.makeConstraints  (make) in
            make.edges.equalTo(self)
        

        title.snp.makeConstraints  (make) in
            make.centerY.equalTo(mainView.snp.centerY)
            make.leading.equalTo(mainView).offset(20)
            make.trailing.equalTo(mainView).offset(-20)
        
    


    func setTitle(title: String)  

        self.title.text = title
    


    static var reuseId: String 
        return NSStringFromClass(self)
    


还有细胞:

import UIKit

class SelectIconViewCell: UICollectionViewCell 

    internal let mainView = UIView()
    internal var iconImage = UIImageView()
    internal var label = UILabel()

    override init(frame: CGRect) 
        super.init(frame: frame)
    

    required init?(coder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    


    func initializeUI() 

        self.backgroundColor = UIColor.clear
        self.addSubview(mainView)
        mainView.backgroundColor = UIColor.clear
        mainView.layer.masksToBounds = true
        mainView.layer.borderColor = Theme.Color.backgroundCell.cgColor
        mainView.layer.borderWidth = 1.0

        mainView.addSubview(iconImage)
        iconImage.image = UIImage(named: "icons8-air-conditioner-100")

        mainView.addSubview(label)
        label.font = Theme.Font.footnote()
        label.textAlignment = .center
        label.textColor = Theme.Color.textInfo
        label.numberOfLines = 1
    


    internal func createConstraints() 

        mainView.snp.makeConstraints  (make) in
            make.edges.equalTo(self)
        

        iconImage.snp.makeConstraints  (make) in
            make.center.equalTo(mainView.snp.center)
            make.width.height.equalTo(20)
        

        label.snp.makeConstraints  (make) in
            make.top.equalTo(iconImage.snp.bottom).offset(6)
            make.leading.equalTo(mainView).offset(5)
            make.trailing.equalTo(mainView).offset(-5)
        
    


    func setValues(iconName: String, label: String)  

        //self.iconImage.image = UIImage(named: iconName)
        self.label.text = label
    


    static var reuseId: String 
        return NSStringFromClass(self)
    


【讨论】:

【参考方案5】:

创建和注册自定义页眉(和/或页脚)后,您可以轻松地为不同的部分指定不同的页眉(或页脚)。这是一个例子:

    override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView 
        switch kind 
        case UICollectionElementKindSectionHeader:
            let section = indexPath.section

            switch section 
            case 0:
                let userHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: userHeaderReuseIdentifier, for: indexPath) as! UserHeader
                return userHeader
            default:
                let postHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: postHeaderReuseIdentifier, for: indexPath) as! PostHeader
                return postHeader
            
        case UICollectionElementKindSectionFooter:
            let userFooter = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: userFooterReuseIdentifier, for: indexPath) as! UserFooter
            return userFooter
        default:
            return UICollectionReusableView()
        
    

确保也指定正确的部分数量:

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

【讨论】:

【参考方案6】:

Swift-3 的有效解决方案

i)Create Custom Cell && 对应的xib

   class SectionHeaderView: UICollectionViewCell 
        static let kReuseIdentifier = "SectionHeaderView"
        @IBOutlet weak var invitationsSectionHeader: UILabel!
        @IBOutlet weak var numberOfPerson: UILabel!
 

ii)为 HeaderView 注册自定义集合视图单元格

 self.collectionView.register(UINib(nibName: SectionHeaderView.kReuseIdentifier, bundle: nil), forSupplementaryViewOfKind:UICollectionElementKindSectionHeader, withReuseIdentifier: SectionHeaderView.kReuseIdentifier)

iii)调用委托函数来渲染自定义标题视图。

  func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView 
        switch kind 
        case UICollectionElementKindSectionHeader:
           let  headerView: SectionHeaderView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: SectionHeaderView.kReuseIdentifier, for: indexPath) as! SectionHeaderView
            return headerView
        default:
            return UICollectionReusableView()
        
    

iv)自定义标题视图的提及高度

  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize 
        return CGSize(width:collectionView.frame.size.width, height:30)

【讨论】:

【参考方案7】:

@Tarun 的回答对我很有帮助;我错过了collectionView(_:layout:referenceSizeForHeaderInSection:),这是我需要的,因为有时要显示的数据会被排序,有时不会。

此外,通过将以下内容添加到 UICollectionViewController 中的 viewDidLoad() 来完成将部分标题固定到屏幕顶部(如 Apple 的地址簿应用程序的表格视图中):

if let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout 
    flowLayout.sectionHeadersPinToVisibleBounds = true

【讨论】:

帮了大忙!

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

部分之间的多个自定义 TableviewCell 上的多个自定义 UICollectionView

UICollectionView 中的多个部分

使用 IGListKit 或 UICollectionView 每行多个部分

如何在一个视图中添加多个部分 UICollectionView 单元格

具有多个部分的 UICollectionView

如何拥有多个带有标题的 UICollectionView 并使其水平滑动