返回 导航

Swift

hangge.com

Swift - 让CollectionView里的Section分别设置不同的背景色

作者:hangge | 2017-11-08 08:10
我们知道想要给 UICollectionView 设置背景色只需要通过 backgroundColor 属性即可。但如果想让不同的 Section (分区)能显示不同的背景颜色,UICollectionView 本身就没有提供相关的属性或方法了。
要实现这个效果,需要我们通过自定义布局来实现,下面通过样例进行演示。

1,实现原理

(1)想通过自定义布局实现 Section 背景色,需要创建如下三个类的子类:
  • 继承 UICollectionReusableView 来自定义一个装饰视图(Decoration 视图),用来作为各个分组的背景视图(section's background view)。
  • 继承 UICollectionViewLayoutAttributes 来自定义一个新的布局属性,里面添加一个backgroundColor 属性,用来表示 Section 的背景颜色。
  • 继承 UICollectionViewFlowLayout(默认的 Flow 布局)来自定义一个新布局,在这里我们会计算及返回各个分组背景视图的布局属性(位置、尺寸、颜色)
(2)为方便使用我们还要新增一个协议方法,使得 section 背景色可以在外面通过数据源来设置(就像设置 Cell 视图那样)

2,效果图

通过自定义布局,Collection View 里每个 section 都显示不同颜色的背景。

3,样例代码

(1)自定义布局(SectionBgCollectionViewLayout.swift
import UIKit

//表示我们自定义的分区背景(装饰视图)
private let SectionBg = "SectionBgCollectionReusableView"

//增加自己的协议方法,使其可以像cell那样根据数据源来设置section背景色
protocol SectionBgCollectionViewDelegate: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        backgroundColorForSectionAt section: Int) -> UIColor
}

//定义一个UICollectionViewLayoutAttributes子类作为section背景的布局属性,
//(在这里定义一个backgroundColor属性表示Section背景色)
private class SectionBgCollectionViewLayoutAttributes: UICollectionViewLayoutAttributes {
    
    //背景色
    var backgroundColor = UIColor.white
    
    //所定义属性的类型需要遵从 NSCopying 协议
    override func copy(with zone: NSZone? = nil) -> Any {
        let copy = super.copy(with: zone) as! SectionBgCollectionViewLayoutAttributes
        copy.backgroundColor = self.backgroundColor
        return copy
    }
    
    //所定义属性的类型还要实现相等判断方法(isEqual)
    override func isEqual(_ object: Any?) -> Bool {
        guard let rhs = object as? SectionBgCollectionViewLayoutAttributes else {
            return false
        }
        
        if !self.backgroundColor.isEqual(rhs.backgroundColor) {
            return false
        }
        return super.isEqual(object)
    }
}

//继承UICollectionReusableView来自定义一个装饰视图(Decoration 视图),用来作为Section背景
private class SectionBgCollectionReusableView: UICollectionReusableView {
    
    //通过apply方法让自定义属性生效
    override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
        super.apply(layoutAttributes)
        
        guard let attr = layoutAttributes as? SectionBgCollectionViewLayoutAttributes else
        {
            return
        }
        
        self.backgroundColor = attr.backgroundColor
    }
}

//自定义布局(继承系统内置的 Flow 布局)
class SectionBgCollectionViewLayout: UICollectionViewFlowLayout {
    
    //保存所有自定义的section背景的布局属性
    private var decorationViewAttrs: [UICollectionViewLayoutAttributes] = []
    
    override init() {
        super.init()
        
        setup()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        
        setup()
    }
    
    //初始化时进行一些注册操作
    func setup() {
        //注册我们自定义用来作为Section背景的 Decoration 视图
        self.register(SectionBgCollectionReusableView.classForCoder(),
                      forDecorationViewOfKind: SectionBg)
    }
    
    //对一些布局的准备操作放在这里
    override func prepare() {
        super.prepare()
        
        //如果collectionView当前没有分区,或者未实现相关的代理则直接退出
        guard let numberOfSections = self.collectionView?.numberOfSections,
            let delegate = self.collectionView?.delegate
                as? SectionBgCollectionViewDelegate
            else {
                return
        }
        
        //先删除原来的section背景的布局属性
        self.decorationViewAttrs.removeAll()
        
        //分别计算每个section背景的布局属性
        for section in 0..<numberOfSections {
            //获取该section下第一个,以及最后一个item的布局属性
            guard let numberOfItems = self.collectionView?.numberOfItems(inSection:
                section),
                numberOfItems > 0,
                let firstItem = self.layoutAttributesForItem(at:
                    IndexPath(item: 0, section: section)),
                let lastItem = self.layoutAttributesForItem(at:
                    IndexPath(item: numberOfItems - 1, section: section))
                else {
                    continue
            }
            
            //获取该section的内边距
            var sectionInset = self.sectionInset
            if let inset = delegate.collectionView?(self.collectionView!,
                                    layout: self, insetForSectionAt: section) {
                sectionInset = inset
            }
            
            //计算得到该section实际的位置
            var sectionFrame = firstItem.frame.union(lastItem.frame)
            sectionFrame.origin.x = 0
            sectionFrame.origin.y -= sectionInset.top
            
            //计算得到该section实际的尺寸
            if self.scrollDirection == .horizontal {
                sectionFrame.size.width += sectionInset.left + sectionInset.right
                sectionFrame.size.height = self.collectionView!.frame.height
            } else {
                sectionFrame.size.width = self.collectionView!.frame.width
                sectionFrame.size.height += sectionInset.top + sectionInset.bottom
            }
            
            //更具上面的结果计算section背景的布局属性
            let attr = SectionBgCollectionViewLayoutAttributes(
                forDecorationViewOfKind: SectionBg,
                with: IndexPath(item: 0, section: section))
            attr.frame = sectionFrame
            attr.zIndex = -1
            //通过代理方法获取该section背景使用的颜色
            attr.backgroundColor = delegate.collectionView(self.collectionView!,
                           layout: self, backgroundColorForSectionAt: section)
            
            //将该section背景的布局属性保存起来
            self.decorationViewAttrs.append(attr)
        }
    }
    
    //返回rect范围下所有元素的布局属性(这里我们将自定义的section背景视图的布局属性也一起返回)
    override func layoutAttributesForElements(in rect: CGRect)
        -> [UICollectionViewLayoutAttributes]? {
        var attrs = super.layoutAttributesForElements(in: rect)
        attrs?.append(contentsOf: self.decorationViewAttrs.filter {
            return rect.intersects($0.frame)
        })
        return attrs
    }
    
    //返回对应于indexPath的位置的Decoration视图的布局属性
    override func layoutAttributesForDecorationView(ofKind elementKind: String,
                at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        //如果是我们自定义的Decoration视图(section背景),则返回它的布局属性
        if elementKind == SectionBg {
            return self.decorationViewAttrs[indexPath.section]
        }
        return super.layoutAttributesForDecorationView(ofKind: elementKind,
                                                       at: indexPath)
    }
}

(2)接着打开 StoryBoard,将 Collection View Layout 设置成我们自定义的布局。
       

(3)最后让 Collection View Controller 实现我们定义的 SectionBgCollectionViewDelegate 协议,返回每个 Section 的背景色即可。
import UIKit

//重用单元格identifier
private let cellIdentifier = "Cell"

class CollectionViewController: UICollectionViewController, SectionBgCollectionViewDelegate
{
    override func viewDidLoad() {
        super.viewDidLoad()

        //注册可重用的单元格
        self.collectionView!.register(UICollectionViewCell.self,
                                      forCellWithReuseIdentifier: cellIdentifier)
        
        //将collectionView背景色设为白色
        self.collectionView?.backgroundColor = UIColor.white
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    //返回分区数
    override func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 4
    }

    //返回每个分区下单元格个数
    override func collectionView(_ collectionView: UICollectionView,
                                 numberOfItemsInSection section: Int) -> Int {
        //奇数section里有8个单元格,偶数section里有4个单元格
        return (section % 2 == 1) ? 4 : 8
    }

    //返回每个单元格视图
    override func collectionView(_ collectionView: UICollectionView,
                             cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier,
                                                      for: indexPath)
        cell.backgroundColor = UIColor.white
        return cell
    }
    
    //返回每个分区的内边距
    func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        insetForSectionAt section: Int) -> UIEdgeInsets {
        let numberOfItems = collectionView.numberOfItems(inSection: section)
        return numberOfItems > 0 ? UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) :
            UIEdgeInsets.zero
    }
    
    //返回每个分区的背景色
    func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        backgroundColorForSectionAt section: Int) -> UIColor {
        if section == 0 {
            return UIColor.green
        } else if section == 1 {
            return UIColor.cyan
        } else if section == 2 {
            return UIColor.blue
        }
        return UIColor.purple
    }
}
源码下载:hangge_1844.zip
评论

全部评论(0)

回到顶部