I am subclassing a UICollectionViewCompositionalLayout
, for the purpose of adding custom margins to a decoration view. On vertical layouts it works as expected, however when a section with orthogonal scrolling is introduced, UICollectionViewLayoutAttributes
that do not pass through layoutAttributesForElements
get applied to the DecorationView
and thus breaking the layout during scroll. I suspect the OrthogonalScrollViews apply their own attributes incorrectly thus breaking this code.
FeedbackID: FB13705625
final class CompositionalViewLayout: UICollectionViewCompositionalLayout {
private var decorationViewAttributes: [IndexPath: BackgroundDecoratorLayoutAttributes] = [:]
override func prepare() {
super.prepare()
guard let collectionView = collectionView else {
return
}
decorationViewAttributes.removeAll()
for sectionIndex in 0..<collectionView.numberOfSections {
let numberOfItems = collectionView.numberOfItems(inSection: sectionIndex)
guard numberOfItems > 0,
let firstItem = layoutAttributesForItem(at: IndexPath(item: 0, section: sectionIndex)),
let lastItem = layoutAttributesForItem(
at: IndexPath(item: numberOfItems - 1, section: sectionIndex)
) else { continue }
let indexPath = IndexPath(item: 0, section: sectionIndex)
// Create section frame
var sectionFrame = firstItem.frame.union(lastItem.frame)
sectionFrame.size.width = collectionView.frame.width
sectionFrame.origin.x = 12
sectionFrame.size.width -= 24
createSectionDecorationView(
sectionFrame: sectionFrame,
indexPath: indexPath
)
}
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attributes: [UICollectionViewLayoutAttributes] = super.layoutAttributesForElements(in: rect) ?? []
print("Dinesh :: super Attributes \(attributes)")
var modifiedAttributes = [UICollectionViewLayoutAttributes]()
var addedAttributes = Set<UICollectionViewLayoutAttributes>()
for attribute in attributes {
if let customAttr = decorationViewAttributes[attribute.indexPath], attribute.representedElementKind == customAttr.representedElementKind {
modifiedAttributes.append(customAttr)
addedAttributes.insert(customAttr)
} else {
modifiedAttributes.append(attribute)
}
}
modifiedAttributes.append(contentsOf: decorationViewAttributes.values.filter { return (!addedAttributes.contains($0) && rect.intersects($0.frame)) })
return modifiedAttributes
}
override func layoutAttributesForDecorationView(ofKind elementKind: String,
at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard elementKind == ViewController.sectionBackgroundDecorationElementKind,
let decorationAttribute = decorationViewAttributes[indexPath] else {
return super.layoutAttributesForDecorationView(ofKind: elementKind, at: indexPath)
}
return decorationAttribute
}
private func createSectionDecorationView(sectionFrame: CGRect, indexPath: IndexPath) {
let attr = BackgroundDecoratorLayoutAttributes(
forDecorationViewOfKind: ViewController.sectionBackgroundDecorationElementKind,
with: indexPath
)
attr.frame = sectionFrame
attr.zIndex = -1
attr.cornerRadius = 12
switch indexPath.section {
case 0:
attr.backgroundColor = .systemRed
case 1:
attr.backgroundColor = .systemYellow
case 2:
attr.backgroundColor = .systemBlue
case 3:
attr.backgroundColor = .systemGreen
case 4:
attr.backgroundColor = .systemPink
default:
attr.backgroundColor = .systemGray
}
// Cache attribute
decorationViewAttributes[indexPath] = attr
}
}
class SectionBackgroundDecorationView: UICollectionReusableView {
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
fatalError("not implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
}
override func prepareForReuse() {
super.prepareForReuse()
}
override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
super.apply(layoutAttributes)
guard let attr = layoutAttributes as? BackgroundDecoratorLayoutAttributes else { return }
backgroundColor = attr.backgroundColor
layer.cornerRadius = attr.cornerRadius
}
}