Last active
July 23, 2021 03:34
-
-
Save unknown-undefined/23648e1fadd773466a001a3529d6fb46 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 | |
} | |
} |
计算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
计算Section的背景 高度 有点问题。https://github.com/ericchapman/ios_decoration_view/blob/master/ECDecorationView/ViewController.m
计算就是正确的