先说一下整体的逻辑:由于iOS
自带的UICollectionViewFlowLayout
是不支持每一个section
带有不同的样式,所以要想实现这个就要自定义一个集成自UICollectionViewFlowLayout
的Layout
。下面来看具体的实现:
1、首先创建QSLayoutAttributes
类集成自UICollectionViewLayoutAttributes
,从名字上看我们就知道这个类是存储layout样式属性的类,这里直接上代码了:
import UIKit
class QSLayoutAttributes: UICollectionViewLayoutAttributes {
/// 添加组背景
var backgroundColor:UIColor?
/// 添加组圆角
var corners:UIRectCorner?
/// 添加组圆角的大小
var sectionCornerRadii:CGSize?
}
当然我这里只是简单的定义了几个样式的属性,如果需求可根据自身需求无线的扩展。
2、下面这个类QSReusableView
集成自UICollectionReusableView
这实际上是一个装饰类,这里就是用这个来作为整个组的背景。
class QSReusableView: UICollectionReusableView {
override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
super.apply(layoutAttributes)
if layoutAttributes.isKind(of: QSLayoutAttributes.self) {
let layoutAttribute = layoutAttributes as? QSLayoutAttributes
if layoutAttribute!.backgroundColor != nil {
self.backgroundColor = layoutAttribute!.backgroundColor
}
/// 默认设置为 0 以后有需要可以自定义
if layoutAttribute!.corners != nil {
self.setRadius(0, withRectCorners: layoutAttribute!.corners!)
}
}
}
}
3、那么重点来了,直接上代码
import UIKit
let kDefaultCornerRadii = CGSize(width: 0, height: 0)
class QSCollectionViewLayout: UICollectionViewFlowLayout {
/// 存储添加的属性
private var layoutAttributes:Array<UICollectionViewLayoutAttributes>?
/// CollectionView的边距 这个值可以自定义 默认是0
public var marginValue:CGFloat = 0
override init() {
super.init()
self.layoutAttributes = []
/// 注册一个修饰View,这个UIView就是用来做样式展示的
self.registDecorationView()
}
func registDecorationView() {
self.register(QSReusableView.self, forDecorationViewOfKind: QSReusableView.className())
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// MARK: - override
extension QSCollectionViewLayout{
// NOTE: 该方法会在你每次刷新collection data的时候都会调用
override func prepare() {
super.prepare()
/// 避免属性重复添加数据过大
self.layoutAttributes?.removeAll()
/// 设置背景和圆角
self.setSectionBackgaoundColorAndCorner()
}
/// 返回rect中的所有的元素的布局属性
/// - Parameter rect: rect description
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var attributes = super.layoutAttributesForElements(in: rect)
for attribute in self.layoutAttributes! {
///判断两个区域是否有交集
if rect.intersects(attribute.frame){
attributes?.append(attribute)
}
}
return attributes
}
/// 给Decorationview返回属性数组
override func layoutAttributesForDecorationView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
if elementKind == QSReusableView.className() {
return self.layoutAttributes![indexPath.section]
}
return super.layoutAttributesForDecorationView(ofKind: elementKind, at: indexPath)
}
}
// MARK: - 设置属性
extension QSCollectionViewLayout{
/// 给collectionView设置背景和圆角
func setSectionBackgaoundColorAndCorner(){
/// 获取collectionView的代理
guard let delegate = self.collectionView?.delegate else {
return
}
/// 没遵守这个协议就不再往下设置
if delegate.conforms(to: QSCollectionViewDelegateFlowLayout.self) == false {
return
}
/// collectionView有多少组
let numberOfSections = self.collectionView?.numberOfSections ?? 0
if numberOfSections == 0 {
return
}
/// 循环遍历各组 设置添加的属性
for section in 0..<numberOfSections {
/// 一组cell的Item
let numberOfItems = self.collectionView?.numberOfItems(inSection: section) ?? 0
if (numberOfItems <= 0) {
continue;
}
/// 每一组第一个item的Attributes
let firstItem = self.layoutAttributesForItem(at: IndexPath.init(item: 0, section: section))
/// 每一组最后一个item的Attributes
let lastItem = self.layoutAttributesForItem(at: IndexPath.init(item: numberOfItems - 1, section: section))
/// 满足条件 结束本次循环执行下一次
if ((firstItem == nil) || (lastItem == nil)) {
continue
}
/// 实现了insetForSectionAt
if delegate.responds(to: #selector(UICollectionViewDelegateFlowLayout.collectionView(_:layout:insetForSectionAt:))) {
//边距
let inset = (delegate as? UICollectionViewDelegateFlowLayout)? .collectionView?(self.collectionView!, layout: self, insetForSectionAt: section)
self.sectionInset = inset!
}
/// 获取第一个和最后一个item的联合frame ,得到的就是这一组的frame
var sectionFrame:CGRect = firstItem!.frame.union(lastItem!.frame)
/// 设置它的x.y 注意理解这里的x点和y点的坐标,不要硬搬,下面这样写的时候是把inset的left的
/// 距离包含在sectionFrame 开始x的位置 下面的y同逻辑
sectionFrame.origin.x -= self.sectionInset.left - self.marginValue
sectionFrame.origin.y -= self.sectionInset.top
///横向滚动
if self.scrollDirection == .horizontal{
/// 计算组的宽的时候要把缩进进去的距离加回来 因为缩进是内容缩进
sectionFrame.size.width += self.sectionInset.left + self.sectionInset.right
/// 横向滚动的时候 组的高就是collectionView的高
sectionFrame.size.height = self.collectionView!.frame.size.height
/// 纵向滚动
}else{
/// 纵向滚动的时候组的宽度 这里的道理和上面的x,y的一样,需要你按照自己项目的实际需求去处理
sectionFrame.size.width = self.collectionView!.frame.size.width - (2 * self.marginValue)
sectionFrame.size.height += self.sectionInset.top + self.sectionInset.bottom
}
/// 根据自定义的CollectionViewSectionBackground 装饰View初始化一个自定义的CollectionLayoutAttributes
let attribute = QSLayoutAttributes.init(forDecorationViewOfKind:QSReusableView.className(),with: IndexPath.init(item: 0, section: section))
attribute.frame = sectionFrame
attribute.zIndex = -1
/// 实现了backgroundColorForSection
if delegate.responds(to: #selector(QSCollectionViewDelegateFlowLayout.backgroundColorForSection(collectionView:layout:section:))){
/// 背景色
attribute.backgroundColor = (delegate as? QSCollectionViewDelegateFlowLayout)?.backgroundColorForSection!(collectionView: self.collectionView!, layout: self, section: section)
}
/// 实现了cornerForSection
if delegate.responds(to: #selector(QSCollectionViewDelegateFlowLayout.cornerForSection(collectionView:layout:section:))) {
/// 圆角
attribute.corners = (delegate as? QSCollectionViewDelegateFlowLayout)?.cornerForSection!(collectionView: self.collectionView!, layout: self, section: section)
/// 要是是默认的大小就不在实现cornerRadiiForSection
attribute.sectionCornerRadii = kDefaultCornerRadii;
}
/// 要是自定义了圆角大小
if delegate.responds(to: #selector(QSCollectionViewDelegateFlowLayout.cornerRadiiForSection(collectionView:layout:section:))) { attribute.sectionCornerRadii = (delegate as? QSCollectionViewDelegateFlowLayout)?.cornerRadiiForSection!(collectionView: self.collectionView!, layout: self, section: section)
}
self.layoutAttributes?.append(attribute)
}
}
}
//使用协议来设置具体的样式
@objc protocol QSCollectionViewDelegateFlowLayout:UICollectionViewDelegateFlowLayout{
/// CollectionView 的组设置背景颜色
/// - Returns: return 组的颜色
@objc optional func backgroundColorForSection(
collectionView:UICollectionView,
layout:UICollectionViewLayout,
section:NSInteger) -> UIColor
/// CollectionView 的组设置圆角
/// - Returns: UIRectCorner eg:[.topLeft,.topRight]
@objc optional func cornerForSection(
collectionView:UICollectionView,
layout:UICollectionViewLayout,
section:NSInteger) -> UIRectCorner
/// CollectionView 的组设置圆角的大小 要是默认的0可不实现此方法返回
/// - Returns: CGSize 圆角大小
@objc optional func cornerRadiiForSection(
collectionView:UICollectionView,
layout:UICollectionViewLayout,
section:NSInteger) -> CGSize
}
4、基础代码就是这样了,下面来怎么使用
//UICollectionView的定义
private lazy var collectionV:UICollectionView = {
let layout = QSCollectionViewLayout()
let _cv = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout)
_cv.delegate = self
_cv.dataSource = self
_cv.register(StatisticalCell.self, forCellWithReuseIdentifier: kCellId)
_cv.register(FeeStaisticalCell.self, forCellWithReuseIdentifier: kFeeCellId)
_cv.register(StatisticalHeaderView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: kHeaderId)
return _cv
}()
//实现协议,需要哪些样就实现哪些协议就好了
extension MyVC:QSCollectionViewDelegateFlowLayout{
//section的背景
func backgroundColorForSection(collectionView: UICollectionView, layout: UICollectionViewLayout, section: NSInteger) -> UIColor {
return UIColor(hexString: "ffffff")
}
//section的圆角
func cornerForSection(
collectionView:UICollectionView,
layout:UICollectionViewLayout,
section:NSInteger) -> UIRectCorner{
//列如只需要上方圆角
return [UIRectCorner.topLeft,UIRectCorner.topRight]
}
//圆角大小
func cornerRadiiForSection(
collectionView:UICollectionView,
layout:UICollectionViewLayout,
section:NSInteger) -> CGSize{
return CGSize(width: 10, height: 10)
}