Swift 实现自定义 UICollectionView的 se

作者: 夏天然后 | 来源:发表于2022-06-22 18:03 被阅读0次

    自定义 section 背景

    首先创建一个 section装饰背景注册类, 内部包含一个 UIImageView, 可作为自定义背景填充视图

    class SectionBackgroundReusableView: UICollectionReusableView {
    
        static let BACKGAROUND_CID = "BACKGAROUND_CID"
    
        private lazy var bgImageView = UIImageView()
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            self += bgImageView
        }
    
        override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
            super.apply(layoutAttributes)
            bgImageView.frame = bounds
            guard let att = layoutAttributes as? SectionDecorationViewLayoutAttributes else {
                return
            }
            backgroundColor = UIColor.clear
            bgImageView.layer.cornerRadius = 12.px
            bgImageView.clipsToBounds = true
            bgImageView.backgroundColor = att.backgroundColor
            bgImageView.setImage(url: att.imageName)
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    }
    

    之后定义一个 section装饰视图布局属性类

    class SectionDecorationViewLayoutAttributes: 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? SectionDecorationViewLayoutAttributes
            copy?.imageName = imageName
            copy?.backgroundColor = backgroundColor
            return copy as Any
        }
    
        /// 所定义属性的类型还要实现相等判断方法(isEqual)
        /// - Parameter object:
        /// - Returns: 是否相等
        override func isEqual(_ object: Any?) -> Bool {
            guard let rhs = object as? SectionDecorationViewLayoutAttributes else {
                return false
            }
            if imageName != rhs.imageName {
                return false
            }
            if !backgroundColor.isEqual(rhs.backgroundColor) {
                return false
            }
            return super.isEqual(object)
        }
    }
    

    之后我们自定义UICollectionViewFlowLayout

    class SectionDecorationLayout: UICollectionViewFlowLayout {
    
        weak var decorationDelegate: SectionDecorationLayoutDelegate?
    
        /// 保存所有自定义的section背景的布局属性
        private var decorationBackgroundAttrs: [Int: UICollectionViewLayoutAttributes] = [:]
    
        override init() {
            super.init()
            // 背景View注册
            register(SectionBackgroundReusableView.self,
                     forDecorationViewOfKind: SectionBackgroundReusableView.BACKGAROUND_CID)
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        /// 布局配置数据
        // swiftlint:disable cyclomatic_complexity
        // swiftlint:disable function_body_length
        override func prepare() {
            super.prepare()
    
            guard let collectionV = collectionView else {
                return
            }
            // 如果collectionView当前没有分区,则直接退出
            guard collectionV.numberOfSections != 0 else {
                return
            }
            // 不存在cardDecorationDelegate就退出
            guard let delegate = decorationDelegate else {
                return
            }
            if decorationBackgroundAttrs.isNonEmpty {
                decorationBackgroundAttrs.removeAll()
            }
            for section: Int in 0..<collectionV.numberOfSections {
                // 获取该section下第一个,以及最后一个item的布局属性
                let numberOfItems = collectionV.numberOfItems(inSection: section)
                guard numberOfItems > 0,
                      let firstItem = layoutAttributesForItem(at: IndexPath(item: 0, section: section)),
                      let lastItem = layoutAttributesForItem(at: IndexPath(item: numberOfItems - 1, section: section))
                else {
                    continue
                }
                var sectionInset = sectionInset
    
                // 获取该section的内边距
                let inset = delegate.collectionView(collectionView: collectionV, layout: self, insetForSectionAt: section)
                if inset != .zero {
                    sectionInset = inset
                }
    
                // 获取该section header的size
                let headerSize = delegate.collectionView(collectionView: collectionV,
                                                         layout: self,
                                                         headerForSectionAt: section)
                var sectionFrame: CGRect = .zero
                if 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.origin.x
                    sectionFrame.size.height = collectionV.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 = collectionV.frame.size.width - sectionInset.left - sectionInset.right
                    sectionFrame.size.height -= sectionFrame.origin.y
                }
    
                let attrs = SectionDecorationViewLayoutAttributes(
                    forDecorationViewOfKind: SectionBackgroundReusableView.BACKGAROUND_CID,
                    with: IndexPath(item: 0, section: section)
                )
    
                let backgroundColor = delegate.collectionView(collectionV,
                                                              layout: self,
                                                              decorationColorForSectionAt: section)
                attrs.frame = sectionFrame
                attrs.zIndex = -1
                attrs.backgroundColor = backgroundColor
                // 优先保存颜色
                decorationBackgroundAttrs[section] = attrs
    
                // 判断背景图片是否可见 不可见跳过
                let backgroundDisplayed = delegate.collectionView(collectionV,
                                                                  layout: self,
                                                                  decorationImageDisplayedForSectionAt: section)
                guard backgroundDisplayed else {
                    continue
                }
                //  如果背景图片名称为nil,跳过
                guard let imageName = delegate.collectionView(collectionV,
                                                              layout: self,
                                                              decorationImageForSectionAt: section) else {
                    continue
                }
                
                attrs.imageName = imageName
    
                let displayedFillet = delegate.collectionView(collectionV,
                                                              layout: self,
                                                              filletDisplayedForSectionAt: section)
                guard displayedFillet == false else {
                    continue
                }
            }
        }
        // swiftlint:enable function_body_length
        // swiftlint:enable cyclomatic_complexity
    
        override func layoutAttributesForDecorationView(ofKind elementKind: String,
                                                        at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
            let section = indexPath.section
            if elementKind == SectionBackgroundReusableView.BACKGAROUND_CID {
                return decorationBackgroundAttrs[section]
            }
            return super.layoutAttributesForDecorationView(ofKind: elementKind,
                                                           at: indexPath)
        }
    
        /// 返回rect范围下父类的所有元素的布局属性以及子类自定义装饰视图的布局属性
        override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
            var attrs = super.layoutAttributesForElements(in: rect)
            attrs?.append(contentsOf: decorationBackgroundAttrs.values.filter {
                rect.intersects($0.frame)
            })
            return attrs
        }
    }
    

    之后我们定义好给 layout 使用的 协议, 用来设置相关的属性

    protocol SectionDecorationLayoutDelegate: NSObjectProtocol {
    
        /// Section背景的边距
        /// - Parameters:
        ///   - collectionView: collectionView
        ///   - layout: layout
        ///   - insetForSectionAtIndex: section
        /// - Returns: 边距
        func collectionView(collectionView: UICollectionView,
                            layout collectionViewLayout: SectionDecorationLayout,
                            insetForSectionAt section: Int) -> UIEdgeInsets
    
        /// 获取 Section Header 宽高
        /// - Parameters:
        ///   - collectionView: collectionView
        ///   - layout: layout
        ///   - headerForSectionAtIndex: section
        /// - Returns: 宽高
        func collectionView(collectionView: UICollectionView,
                            layout collectionViewLayout: SectionDecorationLayout,
                            headerForSectionAt section: Int) -> CGSize
    
        /// 获取 section footer 宽高
        /// - Parameters:
        ///   - collectionView: collectionView
        ///   - layout: layout
        ///   - footerForSectionAtIndex: section
        /// - Returns: 宽高
        func collectionView(collectionView: UICollectionView,
                            layout collectionViewLayout: SectionDecorationLayout,
                            footerForSectionAt section: Int) -> CGSize
    
        /// 指定section的背景图片名字,默认为nil
        /// - Parameters:
        ///   - collectionView: collectionView
        ///   - collectionViewLayout: layout
        ///   - section: section
        /// - Returns: 图片字符
        func collectionView(_ collectionView: UICollectionView,
                            layout collectionViewLayout: SectionDecorationLayout,
                            decorationImageForSectionAt section: Int) -> String?
    
        /// 指定section背景图的圆角,默认值为true
        /// - Parameters:
        ///   - collectionView: collectionView
        ///   - collectionViewLayout: layout
        ///   - section: section
        /// - Returns: 图片字符
        func collectionView(_ collectionView: UICollectionView,
                            layout collectionViewLayout: SectionDecorationLayout,
                            filletDisplayedForSectionAt section: Int) -> Bool
    
        /// 指定section是否显示背景图片,默认值为false
        /// - Parameters:
        ///   - collectionView: collectionView
        ///   - collectionViewLayout: layout
        ///   - section: section
        /// - Returns: Bool
        func collectionView(_ collectionView: UICollectionView,
                            layout collectionViewLayout: SectionDecorationLayout,
                            decorationImageDisplayedForSectionAt section: Int) -> Bool
    
        /// 指定section背景颜色,默认为白色
        /// - Parameters:
        ///   - collectionView: collectionView
        ///   - collectionViewLayout: layout
        ///   - section: section
        /// - Returns: UIColor
        func collectionView(_ collectionView: UICollectionView,
                            layout collectionViewLayout: SectionDecorationLayout,
                            decorationColorForSectionAt section: Int) -> UIColor
    }
    

    最后就完成了section 背景的自定义, 把一个 collectionView 的分区自定义灵活设置背景

    如何使用?

    1. 自定义 layout
    private lazy var layout = SectionDecorationLayout().then {
            $0.minimumLineSpacing = 20.px
            $0.minimumInteritemSpacing = 0
            $0.decorationDelegate = self
            $0.sectionInset = UIEdgeInsets(top: 20.px, left: 20.px, bottom: 20.px, right: 20.px)
        }
    
    1. 引用 layout
    private lazy var collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout).then {
            $0.backgroundColor = .clear
            $0.showsVerticalScrollIndicator = false
            $0.delegate = self
            $0.dataSource = self
            $0.registerCell(MineMedalListCell.self)
            $0.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 20.px, right: 0)
            $0.registerHeader(MineMedalListHeaderView.self)
            $0.headerRefreshBlock = { [weak self] in
                guard let `self` = self else { return }
                self.viewModel.loadData(obtainType: self.obtainType)
                self.loadHeaderData()
            }
        }
    
    1. 代理方法
    extension MedalListViewController: SectionDecorationLayoutDelegate {
    
        /// 是否显示单独设置 Section 背景颜色
        func collectionView(_ collectionView: UICollectionView,
                            layout collectionViewLayout: SectionDecorationLayout,
                            decorationColorForSectionAt section: Int) -> UIColor {
            UIColor(0x444444)
        }
    
        /// 是否显示 Section 背景图
        func collectionView(_ collectionView: UICollectionView,
                            layout collectionViewLayout: SectionDecorationLayout,
                            decorationImageDisplayedForSectionAt section: Int) -> Bool {
            true
        }
    
        /// 设置背景图的边距
        func collectionView(collectionView: UICollectionView,
                            layout collectionViewLayout: SectionDecorationLayout,
                            insetForSectionAt section: Int) -> UIEdgeInsets {
            UIEdgeInsets(top: 0, left: 24.px, bottom: 0, right: 24.px)
        }
    
        /// 获取 Section Header 宽高 (设置section背景图是否占据头的size)
        func collectionView(collectionView: UICollectionView,
                            layout collectionViewLayout: SectionDecorationLayout,
                            headerForSectionAt section: Int) -> CGSize {
            CGSize(width: UIScreen.width, height: 90.px)
        }
    

    详细如图


    截屏2022-06-22 下午5.58.12.png

    图片中只用了纯黑灰色背景, 以上代码还可以配置背景的图片.

    以上.

    相关文章

      网友评论

        本文标题:Swift 实现自定义 UICollectionView的 se

        本文链接:https://www.haomeiwen.com/subject/iwyuvrtx.html