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
使用 IGListKit 或 UICollectionView 每行多个部分