Skip to content

Instantly share code, notes, and snippets.

@unknown-undefined
Last active July 23, 2021 03:34
Show Gist options
  • Save unknown-undefined/23648e1fadd773466a001a3529d6fb46 to your computer and use it in GitHub Desktop.
Save unknown-undefined/23648e1fadd773466a001a3529d6fb46 to your computer and use it in GitHub Desktop.
//
// SectionBackgroundFlowLayout.swift
//
// Created by Samuel's on 2020/7/29.
import UIKit
@objc protocol SectionBackgroundFlowLayoutDelegate: NSObjectProtocol {
func sectionsNeedBackgroundColor(in collectionView: UICollectionView, layout: UICollectionViewFlowLayout) -> [Int]
func classForBackgroundViewAttributes() -> UICollectionViewLayoutAttributes.Type
func classForBackgroundView() -> UICollectionReusableView.Type
func configSectionBackgroundViewAttributes(_ attributes: UICollectionViewLayoutAttributes,
layout: UICollectionViewFlowLayout,
at section: Int)
@objc optional func sectionBackgroundViewInset(at section: Int) -> UIEdgeInsets
}
class SectionBackgroundColorLayoutAttributes: UICollectionViewLayoutAttributes {
var color: UIColor?
override func copy(with zone: NSZone? = nil) -> Any {
let new = super.copy(with: zone)
(new as? SectionBackgroundColorLayoutAttributes)?.color = color
return new
}
}
class SectionBackgroundReusableView: UICollectionReusableView {
override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
super.apply(layoutAttributes)
let scLayoutAttributes = layoutAttributes as! SectionBackgroundColorLayoutAttributes
backgroundColor = scLayoutAttributes.color
}
}
class SectionBackgroundFlowLayout: UICollectionViewFlowLayout {
private static let sectionBackgroundColorElement = "sectionBackgroundColorElement"
private var sectionMaxYPairs: [Int: CGFloat] = [:]
private var sectionInsetPairs: [Int: UIEdgeInsets] = [:]
private var sectionLineSpacing: [Int: CGFloat] = [:]
private var sectionBackgroundAttributesPairs: [Int: UICollectionViewLayoutAttributes] = [:]
weak var sectionBackgroundDelegate: SectionBackgroundFlowLayoutDelegate?
// MARK: prepareLayout
override func prepare() {
super.prepare()
guard let delegate = sectionBackgroundDelegate else {
// assertionFailure("SET sectionBackgroundDelegate FIRST!")
return
}
register(delegate.classForBackgroundView(), forDecorationViewOfKind: SectionBackgroundFlowLayout.sectionBackgroundColorElement)
prepareSectionInsetPairs()
prepareSectionLineSpacing()
prepareIndices()
prepareSectionBackgroundAttributes()
}
private func prepareSectionInsetPairs() {
sectionInsetPairs.removeAll()
guard let collectionView = collectionView else {
return
}
for section in 0 ..< collectionView.numberOfSections {
let inset = (collectionView.delegate as? UICollectionViewDelegateFlowLayout)?
.collectionView?(collectionView, layout: self, insetForSectionAt: section) ?? sectionInset
sectionInsetPairs[section] = inset
}
}
private func prepareSectionLineSpacing() {
sectionLineSpacing.removeAll()
guard let collectionView = collectionView else { return }
for section in 0 ..< collectionView.numberOfSections {
let spacing = (collectionView.delegate as? UICollectionViewDelegateFlowLayout)?
.collectionView?(collectionView, layout: self, minimumLineSpacingForSectionAt: section) ?? minimumLineSpacing
sectionLineSpacing[section] = spacing
}
}
private func prepareIndices() {
sectionInsetPairs.removeAll()
sectionMaxYPairs.removeAll()
guard let collectionView = collectionView else { return }
prepareSectionInsetPairs()
let numberOfSections = collectionView.numberOfSections
var currentSectionMaxY: CGFloat = 0
for section in 0 ..< numberOfSections {
let numberOfItems = collectionView.numberOfItems(inSection: section)
guard numberOfItems > 0 else {
// remove may exists decorate
sectionBackgroundAttributesPairs[section] = nil
continue
}
let interitemSpacing = (collectionView.delegate as? UICollectionViewDelegateFlowLayout)?
.collectionView?(collectionView, layout: self, minimumInteritemSpacingForSectionAt: section) ?? minimumInteritemSpacing
let currentSectionInset = sectionInsetPairs[section]!
let sizeArray = (0 ..< numberOfItems)
.map { item in
(collectionView.delegate as? UICollectionViewDelegateFlowLayout)?
.collectionView?(collectionView, layout: self, sizeForItemAt: IndexPath(item: item, section: section)) ?? itemSize
}
let sectionWidth = collectionViewContentSize.width - currentSectionInset.left - currentSectionInset.right
currentSectionMaxY = currentSectionMaxY
+ ((collectionView.delegate as? UICollectionViewDelegateFlowLayout)?.collectionView?(collectionView, layout: self, referenceSizeForHeaderInSection: section).height ?? 0)
+ currentSectionInset.top
var lineWidth: CGFloat = 0
var lineHeight: CGFloat = 0
let widthArray = sizeArray.map { $0.width }
for itemIndex in 0 ..< widthArray.count {
assert(widthArray[itemIndex] <= sectionWidth, "Only Vertical Support!! itemWidth Should <= sectionWidth")
lineHeight = max(sizeArray[itemIndex].height, lineHeight)
let hasNext = widthArray.indices ~= itemIndex + 1
let isLineEnd: Bool = {
if hasNext {
let currentLineWidth = lineWidth + widthArray[itemIndex]
return currentLineWidth + interitemSpacing + widthArray[itemIndex + 1] >= sectionWidth
} else {
return true
}
}()
let lineSpacing = sectionLineSpacing[section]!
switch (hasNext, isLineEnd) {
case (true, true):
// current row is over
currentSectionMaxY = currentSectionMaxY + lineHeight + lineSpacing
lineWidth = 0
lineHeight = 0
case (true, false):
// continue current row
lineWidth = lineWidth + widthArray[itemIndex] + interitemSpacing
case (false, _):
// current row is over
let sectionFooterH = ((collectionView.delegate as? UICollectionViewDelegateFlowLayout)?.collectionView?(collectionView, layout: self, referenceSizeForFooterInSection: section).height ?? 0)
currentSectionMaxY = currentSectionMaxY
+ lineHeight
+ currentSectionInset.bottom
+ sectionFooterH
lineWidth = 0
lineHeight = 0
}
}
sectionMaxYPairs[section] = currentSectionMaxY
}
}
private func prepareSectionBackgroundAttributes() {
sectionBackgroundAttributesPairs.removeAll()
guard let collectionView = collectionView,
let sectionBackgroundDelegate = sectionBackgroundDelegate
else { return }
let sections = sectionBackgroundDelegate
.sectionsNeedBackgroundColor(in: collectionView, layout: self)
.filter { $0 < collectionView.numberOfSections }
sections.forEach { section in
// let inset = sectionInsetPairs[section]!
// ignore inset
let x: CGFloat = 0
let y = sectionMaxYPairs[section - 1] ?? 0
guard let maxY = sectionMaxYPairs[section],
maxY - y > 0
else { return }
let w = collectionView.bounds.width
let h = maxY - y
let attClass = sectionBackgroundDelegate.classForBackgroundViewAttributes()
let att = attClass.init(forDecorationViewOfKind: SectionBackgroundFlowLayout.sectionBackgroundColorElement, with: IndexPath(row: 0, section: section))
let rawframe = CGRect(x: x, y: y, width: w, height: h)
let insets = sectionBackgroundDelegate.sectionBackgroundViewInset?(at: section) ?? .zero
att.frame = rawframe.inset(by: insets)
att.zIndex = -100 // may not ok
sectionBackgroundDelegate.configSectionBackgroundViewAttributes(att, layout: self, at: section)
sectionBackgroundAttributesPairs[section] = att
}
}
// MARK: layoutAttributesForElementsInRect
override func layoutAttributesForDecorationView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard elementKind == SectionBackgroundFlowLayout.sectionBackgroundColorElement
else { return nil }
if let collectionView = collectionView,
let sectionBackgroundDelegate = sectionBackgroundDelegate,
collectionView.numberOfItems(inSection: indexPath.section) == 0 {
// 如果section.numberOfItems 由 > 0 => 0,会call 这个方法,若返回nil会导致assert fail
// 所以返回 att.frame = .zero 的att
let attClass = sectionBackgroundDelegate.classForBackgroundViewAttributes()
let att = attClass.init(forDecorationViewOfKind: SectionBackgroundFlowLayout.sectionBackgroundColorElement, with: IndexPath(row: 0, section: indexPath.section))
att.frame = .zero
return att
}
return sectionBackgroundAttributesPairs[indexPath.section]
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var attributes = super.layoutAttributesForElements(in: rect)
let intersects: [UICollectionViewLayoutAttributes] = sectionBackgroundAttributesPairs.compactMap { pair in
if pair.value.frame.intersects(rect) {
return pair.value
}
return nil
}
attributes?.append(contentsOf: intersects)
return attributes
}
}
@tanpengsccd
Copy link

计算Section的背景 高度 有点问题。https://github.com/ericchapman/ios_decoration_view/blob/master/ECDecorationView/ViewController.m
计算就是正确的

@unknown-undefined
Copy link
Author

计算Section的背景 高度 有点问题。https://github.com/ericchapman/ios_decoration_view/blob/master/ECDecorationView/ViewController.m
计算就是正确的

修复了高度问题。这是生产的代码。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment