《代码地址》
-
一些应用需要如图所示一样在集合视图上让不同分区配置背景图片或颜色。并且背景要占据分区头的高度之类操作,尤其是电商平台类别的应用。但是集合视图本身不支持直接设置背景图片,经过一番研究,可以通过重写UICollectionViewFlowLayout实现这个效果。
自定义UICollectionReusableView类,用来注册section装饰背景View
class SectionBackgroundReusableView: UICollectionReusableView {
static let BACKGAROUND_CID = "BACKGAROUND_CID"
private lazy var bg_imageView = UIImageView().then {
addSubview($0)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
super.apply(layoutAttributes)
bg_imageView.frame = bounds
guard let att = layoutAttributes as? SectionDecorationViewCollectionViewLayoutAttributes else {
return
}
self.backgroundColor = UIColor.clear
bg_imageView.layer.cornerRadius = 5
bg_imageView.clipsToBounds = true
bg_imageView.backgroundColor = att.backgroundColor
guard let imageName = att.imageName else {
self.bg_imageView.image = nil
return
}
guard let image_url = URL(string: imageName) else {
return
}
self.bg_imageView.kf.setImage(with: image_url)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
- 自定义UICollectionViewLayoutAttributes,用来保存section的背景图片和颜色数据
class SectionDecorationViewCollectionViewLayoutAttributes: UICollectionViewLayoutAttributes {
// 装饰背景图片
var imageName: String?
// 背景色
var backgroundColor = UIColor.white
/// 所定义属性的类型需要遵从 NSCopying 协议
/// - Parameter zone:
/// - Returns:
override func copy(with zone: NSZone? = nil) -> Any {
let copy = super.copy(with: zone) as! SectionDecorationViewCollectionViewLayoutAttributes
copy.imageName = self.imageName
copy.backgroundColor = self.backgroundColor
return copy
}
/// 所定义属性的类型还要实现相等判断方法(isEqual)
/// - Parameter object:
/// - Returns: 是否相等
override func isEqual(_ object: Any?) -> Bool {
guard let rhs = object as? SectionDecorationViewCollectionViewLayoutAttributes else {
return false
}
if self.imageName != rhs.imageName {
return false
}
if !self.backgroundColor.isEqual(rhs.backgroundColor) {
return false
}
return super.isEqual(object)
}
}
- 自定义UICollectionViewFlowLayout类
- 先注册背景View
override init() {
super.init()
// 背景View注册
self.register(SectionBackgroundReusableView.self, forDecorationViewOfKind: SectionBackgroundReusableView.BACKGAROUND_CID)
}
- 设置背景的位置和大小
// 布局配置数据
override func prepare() {
super.prepare()
// 如果collectionView当前没有分区,则直接退出
guard let numberOfSections = self.collectionView?.numberOfSections
else {
return
}
// 不存在cardDecorationDelegate就退出
guard let delegate = decorationDelegate else {
return
}
if decorationBackgroundAttrs.count > 0 {
decorationBackgroundAttrs.removeAll()
}
for section:Int in 0.. 0,
let firstItem = self.layoutAttributesForItem(at:
IndexPath(item: 0, section: section)),
let lastItem = self.layoutAttributesForItem(at:
IndexPath(item: numberOfItems - 1, section: section))
else {
continue
}
var sectionInset:UIEdgeInsets = self.sectionInset
/// 获取该section的内边距
let inset:UIEdgeInsets = delegate.collectionView(collectionView: self.collectionView!, layout: self, insetForSectionAt: section)
if !(inset == .zero) {
sectionInset = inset
}
/// 获取该section header的size
let headerSize = delegate.collectionView(collectionView: self.collectionView!, layout: self, headerForSectionAt: section)
var sectionFrame:CGRect = .zero
if self.scrollDirection == .horizontal {
let hx = (firstItem.frame.origin.x) - headerSize.width + sectionInset.left
let hy = (firstItem.frame.origin.y) + sectionInset.top
let hw = ((lastItem.frame.origin.x) + (lastItem.frame.size.width)) - sectionInset.right
let hh = ((lastItem.frame.origin.y) + (lastItem.frame.size.height)) - sectionInset.bottom
sectionFrame = CGRect(x: hx , y: hy, width: hw, height: hh)
sectionFrame.origin.y = sectionInset.top
sectionFrame.size.width = sectionFrame.size.width-sectionFrame.origin.x
sectionFrame.size.height = self.collectionView!.frame.size.height - sectionInset.top - sectionInset.bottom
} else {
let vx = (firstItem.frame.origin.x)
let vy = (firstItem.frame.origin.y) - headerSize.height + sectionInset.top
let vw = ((lastItem.frame.origin.x) + (lastItem.frame.size.width))
let vh = ( (lastItem.frame.origin.y) + (lastItem.frame.size.height) ) - sectionInset.bottom
sectionFrame = CGRect(x: vx , y: vy, width: vw, height: vh + 10)
sectionFrame.origin.x = sectionInset.left
sectionFrame.size.width = (self.collectionView?.frame.size.width)! - sectionInset.left - sectionInset.right
sectionFrame.size.height = sectionFrame.size.height - sectionFrame.origin.y
}
let attrs = SectionDecorationViewCollectionViewLayoutAttributes(forDecorationViewOfKind: SectionBackgroundReusableView.BACKGAROUND_CID, with: IndexPath(item: 0, section: section))
let backgroundColor = delegate.collectionView(self.collectionView!, layout: self, decorationColorForSectionAt: section)
attrs.frame = sectionFrame
attrs.zIndex = -1
attrs.backgroundColor = backgroundColor
/// 优先保存颜色
self.decorationBackgroundAttrs[section] = attrs
/// 判断背景图片是否可见 不可见跳过
let backgroundDisplayed = delegate.collectionView(self.collectionView!, layout: self, decorationImgaeDisplayedForSectionAt: section)
guard backgroundDisplayed == true else {
continue
}
/// 如果背景图片名称为nil,跳过
guard let imageName = delegate.collectionView(self.collectionView!, layout: self, decorationImageForSectionAt: section) else {
continue
}
attrs.imageName = imageName
let displayedFillet = delegate.collectionView(self.collectionView!, layout: self, filletDisplayedForSectionAt: section)
guard displayedFillet == false else {
continue
}
// 将该section的布局属性保存起来
self.decorationBackgroundAttrs[section] = attrs
}
}
- 这个方法比较重要,collectionView的分区可以设置背景这是重点,重写DecorationView。Decoration View是UICollectionView的装饰视图,这是苹果官方解释,
// If the layout supports any supplementary or decoration view types, it should also implement the respective atIndexPath: methods for those types.
//如果布局支持任何补充或装饰视图类型,则还应该为这些类型实现相应的atIndexPath:方法。
override func layoutAttributesForDecorationView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let section = indexPath.section
if elementKind == SectionBackgroundReusableView.BACKGAROUND_CID {
return self.decorationBackgroundAttrs[section]
}
return super.layoutAttributesForDecorationView(ofKind: elementKind,
at: indexPath)
}
-
重要的代码都做了解释,如果刚好需要这个效果的,可以下载代码看看,再结合自身的需求进行修改。代码地址在前面已经贴出来了