最近开发项目中使用CollectionView遇到对于不同的Section使用不同的颜色的需求,本打算对不同分区的cell 做处理,后来在UICollectionViewLayout的API中发现
open func register(_ viewClass: AnyClass?, forDecorationViewOfKind elementKind: String)
方法,进过搜索发现这个方法确实可以满子需要,比对cell做处理还要方便高效,仅以此文为记录做个简单的总结,deom效果如下
这里需要说明的是UICollectionView中其实有三种 子试图 分别是 DecorationView(装饰图) 、SupplementaryView(分区头/分区尾)、cell(项目) 三种视图。
这里是通过修改DecorationView的frame和背景达到对每个分区做不同的颜色
import UIKit
class DecorationViewController: BaseViewController {
lazy var collectionLayout = {
let layout = DecorationCollectionViewLayout()
layout.minimumLineSpacing = 10
layout.minimumLineSpacing = 10
layout.sectionInset = UIEdgeInsets(top: 5, left: 10, bottom: 5, right: 10)
layout.delegate = self
return layout
}()
lazy var collectionView: UICollectionView = {
let cv = UICollectionView(frame: .zero, collectionViewLayout: collectionLayout)
cv.dataSource = self
cv.delegate = self
cv.register(DecorationCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: DecorationCollectionViewCell.self))
cv.register(DecorationCollectionViewSectionHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: String(describing: DecorationCollectionViewSectionHeader.self))
cv.register(DecorationCollectionViewSectionFooter.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: String(describing: DecorationCollectionViewSectionFooter.self))
cv.backgroundColor = .white
return cv
}()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
makeLayout()
}
func setupUI() {
view.addSubview(collectionView)
}
func makeLayout() {
collectionView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}
import UIKit
class DecorationViewController: BaseViewController {
lazy var collectionLayout = {
let layout = DecorationCollectionViewLayout()
layout.minimumLineSpacing = 10
layout.minimumLineSpacing = 10
layout.sectionInset = UIEdgeInsets(top: 5, left: 10, bottom: 5, right: 10)
layout.delegate = self
return layout
}()
lazy var collectionView: UICollectionView = {
let cv = UICollectionView(frame: .zero, collectionViewLayout: collectionLayout)
cv.dataSource = self
cv.delegate = self
cv.register(DecorationCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: DecorationCollectionViewCell.self))
cv.register(DecorationCollectionViewSectionHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: String(describing: DecorationCollectionViewSectionHeader.self))
cv.register(DecorationCollectionViewSectionFooter.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: String(describing: DecorationCollectionViewSectionFooter.self))
cv.backgroundColor = .white
return cv
}()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
makeLayout()
}
func setupUI() {
view.addSubview(collectionView)
}
func makeLayout() {
collectionView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}
实现对于代理方法
extension DecorationViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return Int.random(in: 4...10)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: DecorationCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: DecorationCollectionViewCell.self), for: indexPath) as! DecorationCollectionViewCell
cell.showCell(title: "index \(indexPath.section) item \(indexPath.item)")
return cell
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if kind == UICollectionView.elementKindSectionFooter {
let supplementaryView: DecorationCollectionViewSectionFooter = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: String(describing: DecorationCollectionViewSectionFooter.self), for: indexPath) as! DecorationCollectionViewSectionFooter
supplementaryView.showHeader(title: "section footer view \(indexPath.section)")
return supplementaryView
}
let supplementaryView: DecorationCollectionViewSectionHeader = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: String(describing: DecorationCollectionViewSectionHeader.self), for: indexPath) as! DecorationCollectionViewSectionHeader
supplementaryView.showHeader(title: "section header view \(indexPath.section)")
return supplementaryView
}
}
extension DecorationViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// 随机生成宽度
let randomWidth = CGFloat.random(in: 50...200) // 示例范围,根据需要调整
let height: CGFloat = 100 // 示例高度,根据需要调整
return CGSize(width: randomWidth, height: height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
let randomHeight = CGFloat.random(in: 50...100)
let width: CGFloat = 100
return CGSize(width: width, height: randomHeight)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
let randomHeight = CGFloat.random(in: 30...80)
let width: CGFloat = 100
return CGSize(width: width, height: randomHeight)
}
}
extension DecorationViewController: DecorationCollectionViewLayoutDelegate {
func layout(_ layout: DecorationCollectionViewLayout, decorationColorAt: Int) -> UIColor {
return UIColor(red: CGFloat.random(in: 0...1), green: CGFloat.random(in: 0...1), blue: CGFloat.random(in: 0...1), alpha: CGFloat.random(in: 0...1))
}
// func layout(_ layout: DecorationCollectionViewLayout, decorationImageAt: Int) -> String {
//
// return String(decorationImageAt)
// }
}
extension DecorationViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return Int.random(in: 4...10)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: DecorationCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: DecorationCollectionViewCell.self), for: indexPath) as! DecorationCollectionViewCell
cell.showCell(title: "index \(indexPath.section) item \(indexPath.item)")
return cell
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if kind == UICollectionView.elementKindSectionFooter {
let supplementaryView: DecorationCollectionViewSectionFooter = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: String(describing: DecorationCollectionViewSectionFooter.self), for: indexPath) as! DecorationCollectionViewSectionFooter
supplementaryView.showHeader(title: "section footer view \(indexPath.section)")
return supplementaryView
}
let supplementaryView: DecorationCollectionViewSectionHeader = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: String(describing: DecorationCollectionViewSectionHeader.self), for: indexPath) as! DecorationCollectionViewSectionHeader
supplementaryView.showHeader(title: "section header view \(indexPath.section)")
return supplementaryView
}
}
extension DecorationViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// 随机生成宽度
let randomWidth = CGFloat.random(in: 50...200) // 示例范围,根据需要调整
let height: CGFloat = 100 // 示例高度,根据需要调整
return CGSize(width: randomWidth, height: height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
let randomHeight = CGFloat.random(in: 50...100)
let width: CGFloat = 100
return CGSize(width: width, height: randomHeight)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
let randomHeight = CGFloat.random(in: 30...80)
let width: CGFloat = 100
return CGSize(width: width, height: randomHeight)
}
}
extension DecorationViewController: DecorationCollectionViewLayoutDelegate {
func layout(_ layout: DecorationCollectionViewLayout, decorationColorAt: Int) -> UIColor {
return UIColor(red: CGFloat.random(in: 0...1), green: CGFloat.random(in: 0...1), blue: CGFloat.random(in: 0...1), alpha: CGFloat.random(in: 0...1))
}
// func layout(_ layout: DecorationCollectionViewLayout, decorationImageAt: Int) -> String {
//
// return String(decorationImageAt)
// }
}
DecorationViewController自定义VC,DecorationCollectionViewLayout负责布局。
这里需要详细介绍一下DecorationCollectionViewLayout
在初始化DecorationCollectionViewLayout的时候注册装饰图register(SectionDecorationView.self, forDecorationViewOfKind: decorationViewKind)
override init() {
super.init()
// 注册装饰图
register(SectionDecorationView.self, forDecorationViewOfKind: decorationViewKind)
}
后面在prepare或layoutAttributesForElements方法中对DecorationView装饰图进行布局
详细代码:
import UIKit
protocol DecorationCollectionViewLayoutDelegate: AnyObject {
// 背景色
func layout(_ layout: DecorationCollectionViewLayout, decorationColorAt: Int) -> UIColor
// 背景图
func layout(_ layout: DecorationCollectionViewLayout, decorationImageAt: Int) -> String
}
extension DecorationCollectionViewLayoutDelegate {
// 背景色
func layout(_ layout: DecorationCollectionViewLayout, decorationColorAt: Int) -> UIColor {
return .white
}
// 背景图
func layout(_ layout: DecorationCollectionViewLayout, decorationImageAt: Int) -> String {
return ""
}
}
// 布局对象
class DecorationCollectionViewLayout: UICollectionViewFlowLayout {
let decorationViewKind = "SectionDecorationView"
var itemsAttribute = [UICollectionViewLayoutAttributes]()
var delegate: DecorationCollectionViewLayoutDelegate?
override init() {
super.init()
// 注册装饰图
register(SectionDecorationView.self, forDecorationViewOfKind: decorationViewKind)
}
override func prepare() {
super.prepare()
itemsAttribute.removeAll() // 清空
// 只有一个装饰图
// decorationOnlyView()
// 装饰每个section
decorationEveryoneSectionView()
// 装饰每个item
// decorationEveryoneItemView()
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var attributes = super.layoutAttributesForElements(in: rect)
for attribute in itemsAttribute { // 可视区域添加对应的装饰图布局
if rect.intersects(attribute.frame) {
attributes?.append(attribute)
}
}
return attributes
}
// 装饰每个item
func decorationEveryoneItemView() {
// 分区数量
let sectionCount = collectionView?.numberOfSections ?? 0
for section in 0..<sectionCount {
let itemCount = collectionView?.numberOfItems(inSection: section) ?? 0
for item in 0..<itemCount { // 每个cell 对应一个 装饰图
let indexPath = IndexPath(item: item, section: section)
let attribute = SectionDecorationAttributes(forDecorationViewOfKind: decorationViewKind, with: indexPath)
//attribute.zIndex = -1
attribute.imageName = delegate?.layout(self, decorationImageAt: section)
let itemAttributes = layoutAttributesForItem(at: indexPath)!
attribute.frame = itemAttributes.frame
self.itemsAttribute.append(attribute)
}
}
}
// 装饰每个Section
func decorationEveryoneSectionView() {
// 分区数量
let sectionCount = collectionView?.numberOfSections ?? 0
// 给每一个section添加装饰视图的布局属性
for section in 0..<sectionCount {
let attribute = SectionDecorationAttributes(forDecorationViewOfKind: decorationViewKind, with: IndexPath(item: 0, section: section))
attribute.zIndex = -1 // 避免遮挡 cell
attribute.imageName = delegate?.layout(self, decorationImageAt: section)
attribute.backgroundColor = delegate?.layout(self, decorationColorAt: section)
// 分区头
var firstItem = layoutAttributesForSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, at: IndexPath(item: 0, section: section))
// 分区尾
var lastItem = layoutAttributesForSupplementaryView(ofKind: UICollectionView.elementKindSectionFooter, at: IndexPath(item: 0, section: section))
guard let count = collectionView?.numberOfItems(inSection: section), count > 0 else {
return
}
if firstItem == nil || firstItem?.frame.height == 0 {
// 第一个
firstItem = layoutAttributesForItem(at: IndexPath(item: 0, section: section))
}
if lastItem == nil || lastItem?.frame.height == 0 {
// 最后一个
lastItem = layoutAttributesForItem(at: IndexPath(item: count - 1, section: section))
}
var sectionFrame = firstItem!.frame.union(lastItem!.frame)
sectionFrame.origin.x = 0
sectionFrame.origin.y = firstItem?.frame.minY ?? 0
sectionFrame.size.width = collectionView?.bounds.width ?? 0
sectionFrame.size.height = (lastItem?.frame.maxY ?? 0) - (firstItem?.frame.minY ?? 0)
attribute.frame = sectionFrame
itemsAttribute.append(attribute)
}
}
// 只有一个装饰图
func decorationOnlyView() {
// 分区数量
let sectionCount = collectionView?.numberOfSections ?? 0
if let itemCountInSection = collectionView?.numberOfItems(inSection: (sectionCount - 1)), itemCountInSection > 0 {
var firstItem = layoutAttributesForSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, at: IndexPath(item: 0, section: 0))
if firstItem == nil || firstItem?.frame.height == 0 {
firstItem = self.layoutAttributesForItem(at: IndexPath(item: 0, section: 0))
}
var lastItem = layoutAttributesForSupplementaryView(ofKind: UICollectionView.elementKindSectionFooter, at: IndexPath(item: 0, section: sectionCount - 1))
if lastItem == nil || lastItem?.frame.height == 0 {
lastItem = self.layoutAttributesForItem(at: IndexPath(item: (itemCountInSection - 1), section: (sectionCount - 1)))
}
let indexPath = IndexPath(item: 0, section: 0)
let attribute = SectionDecorationAttributes(forDecorationViewOfKind: decorationViewKind, with: indexPath)
attribute.zIndex = -1
attribute.imageName = "2"
let height = lastItem!.frame.maxY - firstItem!.frame.minY
attribute.frame = CGRect(x: 0, y: 0, width: collectionView!.frame.size.width, height: height)
self.itemsAttribute.append(attribute)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// 装饰图
class SectionDecorationView: UICollectionReusableView {
var imageView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
//layer.zPosition = -1 // 放底层,避免遮挡 cell
addSubview(imageView)
layer.cornerRadius = 15
}
override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
super.apply(layoutAttributes)
imageView.frame = layoutAttributes.bounds
// 从 layoutAttributes 传递 section 信息
if let attribute = layoutAttributes as? SectionDecorationAttributes {
backgroundColor = attribute.backgroundColor
imageView.image = UIImage(named: attribute.imageName ?? "")
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// 装饰图属性
class SectionDecorationAttributes: UICollectionViewLayoutAttributes {
var backgroundColor: UIColor?
var imageName: String?
}
mport UIKit
protocol DecorationCollectionViewLayoutDelegate: AnyObject {
// 背景色
func layout(_ layout: DecorationCollectionViewLayout, decorationColorAt: Int) -> UIColor
// 背景图
func layout(_ layout: DecorationCollectionViewLayout, decorationImageAt: Int) -> String
}
extension DecorationCollectionViewLayoutDelegate {
// 背景色
func layout(_ layout: DecorationCollectionViewLayout, decorationColorAt: Int) -> UIColor {
return .white
}
// 背景图
func layout(_ layout: DecorationCollectionViewLayout, decorationImageAt: Int) -> String {
return ""
}
}
// 布局对象
class DecorationCollectionViewLayout: UICollectionViewFlowLayout {
let decorationViewKind = "SectionDecorationView"
var itemsAttribute = [UICollectionViewLayoutAttributes]()
var delegate: DecorationCollectionViewLayoutDelegate?
override init() {
super.init()
// 注册装饰图
register(SectionDecorationView.self, forDecorationViewOfKind: decorationViewKind)
}
override func prepare() {
super.prepare()
itemsAttribute.removeAll() // 清空
// 只有一个装饰图
// decorationOnlyView()
// 装饰每个section
decorationEveryoneSectionView()
// 装饰每个item
// decorationEveryoneItemView()
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var attributes = super.layoutAttributesForElements(in: rect)
for attribute in itemsAttribute { // 可视区域添加对应的装饰图布局
if rect.intersects(attribute.frame) {
attributes?.append(attribute)
}
}
return attributes
}
// 装饰每个item
func decorationEveryoneItemView() {
// 分区数量
let sectionCount = collectionView?.numberOfSections ?? 0
for section in 0..<sectionCount {
let itemCount = collectionView?.numberOfItems(inSection: section) ?? 0
for item in 0..<itemCount { // 每个cell 对应一个 装饰图
let indexPath = IndexPath(item: item, section: section)
let attribute = SectionDecorationAttributes(forDecorationViewOfKind: decorationViewKind, with: indexPath)
//attribute.zIndex = -1
attribute.imageName = delegate?.layout(self, decorationImageAt: section)
let itemAttributes = layoutAttributesForItem(at: indexPath)!
attribute.frame = itemAttributes.frame
self.itemsAttribute.append(attribute)
}
}
}
// 装饰每个Section
func decorationEveryoneSectionView() {
// 分区数量
let sectionCount = collectionView?.numberOfSections ?? 0
// 给每一个section添加装饰视图的布局属性
for section in 0..<sectionCount {
let attribute = SectionDecorationAttributes(forDecorationViewOfKind: decorationViewKind, with: IndexPath(item: 0, section: section))
attribute.zIndex = -1 // 避免遮挡 cell
attribute.imageName = delegate?.layout(self, decorationImageAt: section)
attribute.backgroundColor = delegate?.layout(self, decorationColorAt: section)
// 分区头
var firstItem = layoutAttributesForSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, at: IndexPath(item: 0, section: section))
// 分区尾
var lastItem = layoutAttributesForSupplementaryView(ofKind: UICollectionView.elementKindSectionFooter, at: IndexPath(item: 0, section: section))
guard let count = collectionView?.numberOfItems(inSection: section), count > 0 else {
return
}
if firstItem == nil || firstItem?.frame.height == 0 {
// 第一个
firstItem = layoutAttributesForItem(at: IndexPath(item: 0, section: section))
}
if lastItem == nil || lastItem?.frame.height == 0 {
// 最后一个
lastItem = layoutAttributesForItem(at: IndexPath(item: count - 1, section: section))
}
var sectionFrame = firstItem!.frame.union(lastItem!.frame)
sectionFrame.origin.x = 0
sectionFrame.origin.y = firstItem?.frame.minY ?? 0
sectionFrame.size.width = collectionView?.bounds.width ?? 0
sectionFrame.size.height = (lastItem?.frame.maxY ?? 0) - (firstItem?.frame.minY ?? 0)
attribute.frame = sectionFrame
itemsAttribute.append(attribute)
}
}
// 只有一个装饰图
func decorationOnlyView() {
// 分区数量
let sectionCount = collectionView?.numberOfSections ?? 0
if let itemCountInSection = collectionView?.numberOfItems(inSection: (sectionCount - 1)), itemCountInSection > 0 {
var firstItem = layoutAttributesForSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, at: IndexPath(item: 0, section: 0))
if firstItem == nil || firstItem?.frame.height == 0 {
firstItem = self.layoutAttributesForItem(at: IndexPath(item: 0, section: 0))
}
var lastItem = layoutAttributesForSupplementaryView(ofKind: UICollectionView.elementKindSectionFooter, at: IndexPath(item: 0, section: sectionCount - 1))
if lastItem == nil || lastItem?.frame.height == 0 {
lastItem = self.layoutAttributesForItem(at: IndexPath(item: (itemCountInSection - 1), section: (sectionCount - 1)))
}
let indexPath = IndexPath(item: 0, section: 0)
let attribute = SectionDecorationAttributes(forDecorationViewOfKind: decorationViewKind, with: indexPath)
attribute.zIndex = -1
attribute.imageName = "2"
let height = lastItem!.frame.maxY - firstItem!.frame.minY
attribute.frame = CGRect(x: 0, y: 0, width: collectionView!.frame.size.width, height: height)
self.itemsAttribute.append(attribute)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// 装饰图
class SectionDecorationView: UICollectionReusableView {
var imageView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
//layer.zPosition = -1 // 放底层,避免遮挡 cell
addSubview(imageView)
layer.cornerRadius = 15
}
override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
super.apply(layoutAttributes)
imageView.frame = layoutAttributes.bounds
// 从 layoutAttributes 传递 section 信息
if let attribute = layoutAttributes as? SectionDecorationAttributes {
backgroundColor = attribute.backgroundColor
imageView.image = UIImage(named: attribute.imageName ?? "")
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// 装饰图属性
class SectionDecorationAttributes: UICollectionViewLayoutAttributes {
var backgroundColor: UIColor?
var imageName: String?
}
这里的SectionDecorationView就是装饰图 负责装饰UICollectionView。register(_ viewClass: AnyClass?, forDecorationViewOfKind elementKind: String) 装饰图可以装饰整个UICollectionView,也可装饰对象的Section 以及 装饰cell。做的对UICollectionView美化操作