やりたいこと
サーバーから、カラーコードが返ってきた場合に、 カラーコードをCollectionViewのSection背景色に反映する。
CompositionalLayout
は使用してない。
調べたこと
DecorationViewは動的に変えるものじゃない
CompositionalLayoutにdecorationItemsがあったから、普通のCollectionViewにもそんな感じの背景色変える方法があるのでは?そもそもdecorationItemsは動的に変えられるものなのか?と疑問に思い調べたけど、動的に変える方法はなさそうだった。ChatGPTにも聞いてみたが、怒られた。
UICollectionElementCategoryについて
UICollectionViewLayoutには、以下の要素が重要。
-
UICollectionElementCategory.cell
: セル -
UICollectionElementCategory.supplementaryView
: ヘッダやフッタなどの補助的なView -
UICollectionElementCategory.decorationView
: 区切り線や背景などの装飾View
表示する情報を変えたり、UIを提供する場合は.supplementaryView
を使用し、決まったデザインを表示するだけであれば.decorationView
を用いる。
HeaderやFooterのようにsupplementaryViewを登録しておいて、動的に内容を変えられれば良いのかなー
ちなみにDecorationViewを使用する方法については?
- UICollectionReusableViewを継承したDecorationViewを作成
class BackgroundDecorationView: UICollectionReusableView {
override init(frame: CGRect) {
super.init(frame: frame)
configureView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func configureView() {
backgroundColor = UIColor.lightGray // ここで背景色を設定
}
}
- UICollectionViewFlowLayoutを継承してカスタムクラス作成
以下は例なのでこんな感じというだけ
class CustomFlowLayout: UICollectionViewFlowLayout {
override init() {
super.init()
setupLayout()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupLayout() {
// デコレーションビューの登録
register(BackgroundDecorationView.self, forDecorationViewOfKind: "BackgroundDecorationView")
}
// rect内にある要素のレイアウト情報を求めるメソッドをオーバーライドしてDecorationViewを作成して返す
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let layoutAttributes = super.layoutAttributesForElements(in: rect) else { return nil }
var allAttributes = layoutAttributes
// セクションごとにデコレーションビューのレイアウト属性を追加
for section in 0..<collectionView!.numberOfSections {
let indexPath = IndexPath(item: 0, section: section)
if let decorationAttributes = layoutAttributesForDecorationView(ofKind: "BackgroundDecorationView", at: indexPath) {
allAttributes.append(decorationAttributes)
}
}
return allAttributes
}
override func layoutAttributesForDecorationView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attributes = UICollectionViewLayoutAttributes(forDecorationViewOfKind: elementKind, with: indexPath)
// ここでデコレーションビューのサイズや位置を設定
// ItemCellなどから求める必要ある
attributes.frame = xxx
attributes.zIndex = -1
return attributes
}
}
DecorationView毎に背景色を変えたい場合は、UICollectionViewLayoutAttributes
を拡張して、UICollectionViewLayoutAttributesをインスタンス化した際に、colorを変更できるようにするらしい。
supplementaryViewを使用して背景色を動的に変えるやり方
1. UICollectionReusableViewでカスタムクラス作成
import UIKit
final class SectionBackgroundView: UICollectionReusableView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
2. UICollectionViewFlowLayoutを継承してカスタムクラスを作成
import UIKit
class CustomCollectionViewFlowLayout: UICollectionViewFlowLayout {
static let collectionElementKindSectionBackground = "UICollectionElementKindSectionBackground"
private var sectionBackgroundAttributes: [Int: UICollectionViewLayoutAttributes] = [:]
override init() {
super.init()
}
override func prepare() {
super.prepare()
// prepareで計算を行うことでカクツキにくくする
prepareSectionAttribute()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func prepareSectionAttribute() {
guard let collectionView = self.collectionView else { return }
self.sectionBackgroundAttributes.removeAll()
for section in 0..<collectionView.numberOfSections {
guard let minCell = super.layoutAttributesForItem(at: IndexPath(row: 0, section: section)),
let maxCell = super.layoutAttributesForItem(at: IndexPath(row: collectionView.numberOfItems(inSection: section) - 1, section: section)) else {
return
}
let height = maxCell.frame.maxY - minCell.frame.minY + self.sectionInset.bottom
let attributes = UICollectionViewLayoutAttributes(
forSupplementaryViewOfKind: CustomCollectionViewFlowLayout.collectionElementKindSectionBackground,
with: IndexPath(row: 0, section: section))
attributes.zIndex = -1
attributes.frame = CGRect(x: 0, y: minCell.frame.minY - self.sectionInset.top, width: collectionView.bounds.width, height: height)
self.sectionBackgroundAttributes[section] = attributes
}
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard collectionView?.dataSource != nil else { return nil }
guard var allAttributes =
super.layoutAttributesForElements(in: rect) else { return nil }
allAttributes.append(contentsOf: self.sectionBackgroundAttributes.values.filter { $0.frame.intersects(rect) })
return allAttributes
}
override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
switch elementKind {
case CustomCollectionViewFlowLayout.collectionElementKindSectionBackground:
guard indexPath.item == 0 else { return nil }
return self.sectionBackgroundAttributes[indexPath.section]
default:
return super.layoutAttributesForSupplementaryView(ofKind: elementKind, at: indexPath)
}
}
}
- layoutAttributesForElementsの中で計算を行うと、カクツキの原因になるそうなので、
prepare()
で行なっている。 - Sectionの位置計算については、どこまでを範囲とするかなど違ってくると思うので、臨機応変に対応できるよう修正していく。
-
layoutAttributesForElements
で既存のItemに加えて返す -
layoutAttributesForSupplementaryView
をオーバーライドしてkindを検証し、当てはまる場合に、保持しておいた配列から値を返すようにする
3. CollectionViewへの登録(header, footer同様)
collectionView.register(SectionBackgroundView.self, forSupplementaryViewOfKind: CustomCollectionViewFlowLayout.collectionElementKindSectionBackground, withReuseIdentifier: "SectionBackgroundView")
4. Delegateメソッドの実装
// configure
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case CustomCollectionViewFlowLayout.collectionElementKindSectionBackground: // section background
return collectionView.dequeueReusableSupplementaryView(ofKind: CustomCollectionViewFlowLayout.collectionElementKindSectionBackground, withReuseIdentifier: "SectionBackgroundView", for: indexPath)
case foo: // else
return bar
}
}
まとめ
他にも方法があったのでより良いやり方は試して行きたいー
てか、Section背景色変えるのこんなめんどくさいのかや!びっくり
参考
- UICollectionViewLayoutの継承したカスタムクラスでSectionの位置、サイズを計算し、SupplementaryViewとして使えるようにた例
- 以下のサイト見るとカスタムクラスを作成して新しくレイアウトを作ることに対してイメージつきやすいかも
- DecorationViewの背景色を変えている例
- 静的にDecorationViewを設定している例