利用 NSCollectionLayoutDecorationItem 設定 collection view section 的 background

從 iOS 13 開始,collection view 利用 UICollectionViewCompositionalLayout 排版時,可以搭配 NSCollectionLayoutDecorationItem 設定 section 的背景,接下來就讓我們試試幾種不同的例子吧。

  • 每個 section 搭配不同的背景顏色
  • 設定 section 背景的 inset & 圓角
  • 客製 section 背景,比方設定背景圖片

開發前的準備

storyboard 畫面如下。

CollectionViewCell 的 outlet imageView 連到 cell 上的 image view。

class CollectionViewCell: UICollectionViewCell {

@IBOutlet weak var imageView: UIImageView!
}

使用 UICollectionViewCompositionalLayout 排版,分成 2 個 section。

class ViewController: UIViewController {

@IBOutlet weak var collectionView: UICollectionView!

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
collectionView.collectionViewLayout = generateLayout()
}

func generateLayout() -> UICollectionViewLayout {
UICollectionViewCompositionalLayout { sectionIndex, environment in
let space: Double = 10
let itemCountInGroup = sectionIndex == 0 ? 1 : 2
let ratio = 1 / Double(itemCountInGroup)
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(ratio), heightDimension: .fractionalWidth(ratio))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: space, leading: space, bottom: space, trailing: space)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(100))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: space, bottom: 0, trailing: space)
section.orthogonalScrollingBehavior = .continuous
return section
}
}

}

extension ViewController: UICollectionViewDataSource {

func numberOfSections(in collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if section == 0 {
return 1
} else {
return 3
}
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "\(CollectionViewCell.self)", for: indexPath) as! CollectionViewCell
if indexPath.section == 0 {
cell.imageView.image = UIImage(named: "book\(indexPath.item + 1)")
} else {
cell.imageView.image = UIImage(named: "Radio Drama \(indexPath.item + 1)")
}
return cell
}
}

結果

每個 section 搭配不同的背景顏色

  • 新增繼承 UICollectionReusableView 的型別

想成為 section 的背景,它的型別必須繼承 UICollectionReusableView,以下我們定義紅色背景 RedBackgroundDecorationView & 黃色背景 YellowBackgroundDecorationView。

class RedBackgroundDecorationView: UICollectionReusableView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .red

}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

class YellowBackgroundDecorationView: UICollectionReusableView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .yellow

}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
  • 註冊要成為 section 背景的 view

呼叫 register(_:forDecorationViewOfKind:)。

override func viewDidLoad() {
super.viewDidLoad()
collectionView.collectionViewLayout = generateLayout()
collectionView.collectionViewLayout.register(RedBackgroundDecorationView.self, forDecorationViewOfKind: "\(RedBackgroundDecorationView.self)")
collectionView.collectionViewLayout.register(YellowBackgroundDecorationView.self, forDecorationViewOfKind: "\(YellowBackgroundDecorationView.self)")
}
  • 設定 section 搭配的背景

設定 section 的 decorationItems,利用 NSCollectionLayoutDecorationItem 的 background(elementKind:) 指定搭配的背景,比方 NSCollectionLayoutDecorationItem.background(elementKind: "\(RedBackgroundDecorationView.self)") 表示搭配的背景是紅色背景 RedBackgroundDecorationView。

func generateLayout() -> UICollectionViewLayout {

UICollectionViewCompositionalLayout { sectionIndex, environment in
let space: Double = 10
let itemCountInGroup = sectionIndex == 0 ? 1 : 2
let ratio = 1 / Double(itemCountInGroup)
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(ratio), heightDimension: .fractionalWidth(ratio))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: space, leading: space, bottom: space, trailing: space)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(100))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: space, bottom: 0, trailing: space)
section.orthogonalScrollingBehavior = .continuous
let redBackgroundDecorationView = NSCollectionLayoutDecorationItem.background(elementKind: "\(RedBackgroundDecorationView.self)")
let yellowBackgroundDecorationView = NSCollectionLayoutDecorationItem.background(elementKind: "\(YellowBackgroundDecorationView.self)")
section.decorationItems = sectionIndex == 0 ? [redBackgroundDecorationView] :[yellowBackgroundDecorationView]
return section
}
}

結果

設定 section 背景的 inset & 圓角

在 RedBackgroundDecorationView 設定實現圓角效果的 cornerRadius。

class RedBackgroundDecorationView: UICollectionReusableView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .red
layer.cornerRadius = 10
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

view controller 的程式。

class ViewController: UIViewController {

@IBOutlet weak var collectionView: UICollectionView!

override func viewDidLoad() {
super.viewDidLoad()
collectionView.collectionViewLayout = generateLayout() collectionView.collectionViewLayout.register(RedBackgroundDecorationView.self, forDecorationViewOfKind: "\(RedBackgroundDecorationView.self)")
}

func generateLayout() -> UICollectionViewLayout {
let space: Double = 10
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(150))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 3)
group.interItemSpacing = .fixed(space)
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: space, leading: space, bottom: space, trailing: space)
let redBackgroundDecorationView = NSCollectionLayoutDecorationItem.background(elementKind: "\(RedBackgroundDecorationView.self)")
redBackgroundDecorationView.contentInsets = NSDirectionalEdgeInsets(top: space, leading: space, bottom: space, trailing: space)
section.decorationItems = [redBackgroundDecorationView]
return UICollectionViewCompositionalLayout(section: section)
}

}

extension ViewController: UICollectionViewDataSource {

func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "\(CollectionViewCell.self)", for: indexPath) as! CollectionViewCell
cell.imageView.image = UIImage(named: "Radio Drama \(indexPath.item + 1)")
return cell
}
}

說明

設定 redBackgroundDecorationView.contentInsets,讓 section 的背景跟上下左右有間距,實現類似卡片的背景效果。

結果

客製 section 背景,比方設定背景圖片

新增 xib 設計 section 的背景,在 UICollectionReusableView 裡加入 image view。

設定圓角弧度。

view controller 的程式。

class ViewController: UIViewController {

@IBOutlet weak var collectionView: UICollectionView!

override func viewDidLoad() {
super.viewDidLoad()
collectionView.collectionViewLayout = generateLayout() collectionView.collectionViewLayout.register(UINib(nibName: "\(ImageBackgroundDecorationView.self)", bundle: nil), forDecorationViewOfKind: "\(ImageBackgroundDecorationView.self)")
}

func generateLayout() -> UICollectionViewLayout {
let space: Double = 10
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(150))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 3)
group.interItemSpacing = .fixed(space)
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: space, leading: space, bottom: space, trailing: space)
let imageBackgroundDecorationView = NSCollectionLayoutDecorationItem.background(elementKind: "\(ImageBackgroundDecorationView.self)")
imageBackgroundDecorationView.contentInsets = NSDirectionalEdgeInsets(top: space, leading: space, bottom: space, trailing: space)
section.decorationItems = [imageBackgroundDecorationView]
return UICollectionViewCompositionalLayout(section: section)
}

}

extension ViewController: UICollectionViewDataSource {

func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "\(CollectionViewCell.self)", for: indexPath) as! CollectionViewCell
cell.imageView.image = UIImage(named: "Radio Drama \(indexPath.item + 1)")
return cell
}
}

結果

--

--

彼得潘的 iOS App Neverland
彼得潘的 Swift iOS App 開發問題解答集

彼得潘的iOS App程式設計入門,文組生的iOS App程式設計入門講師,彼得潘的 Swift 程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.com