Collectionview and diffable section with multiple cells - ios

I'm trying to create a custom layout with UICollectionViewLayout and UICollectionViewDiffableDataSource.
I want 1 section with cells and both cells need to scroll horizontally simultaneously.
But right now I can only get the top cell to show, see the picture below.
Here's what I've tried so far:
Create Layout
func createLayout() -> UICollectionViewLayout {
let sectionProvider = { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
guard let sectionKind = Section(rawValue: sectionIndex) else { return nil }
let section: NSCollectionLayoutSection
// orthogonal scrolling section of images
if sectionKind == .image {
let itemSize = NSCollectionLayoutSize(widthDimension: .absolute(self.view.frame.width), heightDimension: .fractionalHeight(0.5))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(self.view.frame.width), heightDimension: .absolute(self.view.frame.height))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .paging
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 10, trailing: 0)
// info
} else if sectionKind == .info {
section = NSCollectionLayoutSection.list(using: .init(appearance: .sidebar), layoutEnvironment: layoutEnvironment)
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
} else {
fatalError("Unknown section!")
}
return section
}
return UICollectionViewCompositionalLayout(sectionProvider: sectionProvider)
}
Configure Datasource
func configureDataSource() {
// data source
dataSource = UICollectionViewDiffableDataSource<Section, Art>(collectionView: collectionView) {
(collectionView, indexPath, item) -> UICollectionViewCell? in
guard let section = Section(rawValue: indexPath.section) else { fatalError("Unknown section") }
switch section {
case .image:
return collectionView.dequeueConfiguredReusableCell(using: self.configuredGridCell(), for: indexPath, item: item)
case .info:
return collectionView.dequeueConfiguredReusableCell(using: self.configuredListCell(), for: indexPath, item: item)
}
}
}
applyInitialSnapshots
func applyInitialSnapshots() {
// set the order for our sections
let sections = Section.allCases
var snapshot = NSDiffableDataSourceSnapshot<Section, Art>()
snapshot.appendSections(sections)
dataSource.apply(snapshot, animatingDifferences: false)
// recents (orthogonal scroller)
var imageSnapshot = NSDiffableDataSourceSectionSnapshot<Art>()
imageSnapshot.append(self.arts)
dataSource.apply(imageSnapshot, to: .image, animatingDifferences: false)
var allSnapshot = NSDiffableDataSourceSectionSnapshot<Art>()
allSnapshot.append(self.arts)
dataSource.apply(allSnapshot, to: .info, animatingDifferences: false)
}

For achieving horizontal scroll you can set property for your section as
section.orthogonalScrollingBehavior = .continuous
The easiest way to achieve behaviour like you expect you can combine different group size
let item1 = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
heightDimension: .fractionalHeight(1)))
let item2 = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
heightDimension: .fractionalHeight(0.2)))
item1.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
item2.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
let group1 = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
heightDimension: .fractionalHeight(0.5)),
subitems: [item1])
let group2 = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
heightDimension: .fractionalHeight(0.5)),
subitems: [item2])
let group3 = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
heightDimension: .fractionalHeight(1)),
subitems: [group1, group2])
let section = NSCollectionLayoutSection(group: group3)
section.orthogonalScrollingBehavior = .continuous
return UICollectionViewCompositionalLayout(section: section)

Related

Horizontal UICollectionViewCompositionalLayout with next cell hint

I'm experimenting with UICollectionViewCompositionalLayout and trying to achieve a similar effect to the Apple Music app, especially to the "hint" indicating there are more cells to scroll:
So far, I have managed to make a similar grid, but I can't make the "visual hint" appear among all screen widths.
This is what I tried:
func createBasicListLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
// we will be working with this part
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalHeight(1.0), heightDimension: .fractionalHeight(1))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
group.edgeSpacing = .init(leading: .fixed(10), top: nil, trailing: .fixed(10), bottom: nil)
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .continuous
section.interGroupSpacing = 0
section.contentInsets = .init(top: 0, leading: 20, bottom: 0, trailing: 20)
let layout = UICollectionViewCompositionalLayout(section: section)
layout.configuration.scrollDirection = .horizontal
return layout
}
Which results in:
How can I make the layout show a hint on a third cell? Thanks in advance!

Dynamic width for center UICollectionView's item

I'm working on calendar in which I need to highlight and expand the central cell. It should looks like this:
First I tried to use a compositionalLayout with NSCollectionLayoutItem->widthDimension: .estimated(24).
Collection rendered correctly, but behaved glitchy when scrolling: wrong interim spacing.
UICollectionViewCompositionalLayout { sectionIndex, layoutEnvironment in
guard let sectionKind = TodaySection(rawValue: sectionIndex) else { return nil }
let layoutItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(
widthDimension: .estimated(24),
heightDimension: .fractionalHeight(1.0)))
layoutItem.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
let groupSize: NSCollectionLayoutSize!
switch sectionKind {
case .main:
groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0))
}
let layoutGroup = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [layoutItem])
layoutGroup.contentInsets = .init(top: 0, leading: 0, bottom: 0, trailing: 0)
layoutGroup.interItemSpacing = NSCollectionLayoutSpacing.fixed(12)
let section = NSCollectionLayoutSection(group: layoutGroup)
section.orthogonalScrollingBehavior = .continuous
return section
}
when the cell becomes the center, then I change dots width constraint with animation and call collectionViewLayout.invalidateLayout() at the end. And here is the result:
I can't find a way to dynamically change the cell width for a CompositionalLayout. Or maybe I should stop using compositionalLayout for this issue and use flowLayout instead?

How do I prevent my section headers from overlapping cells in a compositional layout

I want a header to appear above each section in my compositional layout, but I can't find a way to make them appear above the content rather than overlapping it, here is my layout code:
func createLayout() -> UICollectionViewLayout {
let config = UICollectionViewCompositionalLayoutConfiguration()
config.scrollDirection = .horizontal
config.interSectionSpacing = 16
let layout = UICollectionViewCompositionalLayout(sectionProvider: {
(sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
let leadingItem = NSCollectionLayoutItem(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
heightDimension: .estimated(50)))
let widthFraction: CGFloat = UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height ? 0.2 : 0.8
let containerGroup = NSCollectionLayoutGroup.vertical(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(widthFraction),
heightDimension: .estimated(50)),
subitems: [leadingItem])
let section = NSCollectionLayoutSection(group: containerGroup)
section.boundarySupplementaryItems = [self.makeHeader()]
section.orthogonalScrollingBehavior = .continuous
section.interGroupSpacing = 16
return section
}, configuration: config)
return layout
}
func makeHeader() -> NSCollectionLayoutBoundarySupplementaryItem {
let widthFraction: CGFloat = UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height ? 0.2 : 0.8
let size = NSCollectionLayoutSize(widthDimension: .fractionalWidth(widthFraction),
heightDimension: .estimated(50))
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: size,
elementKind: UICollectionView.elementKindSectionHeader,
alignment: .top)
return sectionHeader
}
and here is a screenshot of what is happening, I need the text that says "Column1" to appear above, not overlapping the column cells:
Here was my solution, but it only allows for fixed-height headers on my sections:
you need to set
section.supplementariesFollowContentInsets = false
and then offset the section:
section.contentInsets = NSDirectionalEdgeInsets(top: 66, leading: 8, bottom: 0, trailing: 8)
func createLayout() -> UICollectionViewLayout {
let config = UICollectionViewCompositionalLayoutConfiguration()
config.scrollDirection = .horizontal
let layout = UICollectionViewCompositionalLayout(sectionProvider: { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
let leadingItem = NSCollectionLayoutItem(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
heightDimension: .estimated(50)))
let widthFraction: CGFloat = UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height ? 0.2 : 0.8
let containerGroup = NSCollectionLayoutGroup.vertical(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(widthFraction),
heightDimension: .estimated(50)),
subitems: [leadingItem])
let section = NSCollectionLayoutSection(group: containerGroup)
section.boundarySupplementaryItems = [self.makeHeader()]
section.supplementariesFollowContentInsets = false
section.orthogonalScrollingBehavior = .continuous
section.interGroupSpacing = 16
section.contentInsets = NSDirectionalEdgeInsets(top: 66, leading: 8, bottom: 0, trailing: 8)
return section
}, configuration: config)
return layout
}
func makeHeader() -> NSCollectionLayoutBoundarySupplementaryItem {
let widthFraction: CGFloat = UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height ? 0.2 : 0.8
let size = NSCollectionLayoutSize(widthDimension: .fractionalWidth(widthFraction),
heightDimension: .absolute(50))
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: size, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)
return sectionHeader
}
Add your header as in boundarySupplementaryItems inside your layout declaration.
let layout = UICollectionViewCompositionalLayout(sectionProvider: {
…
let widthFraction: CGFloat = UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height ? 0.2 : 0.8
let size = NSCollectionLayoutSize(widthDimension: .fractionalWidth(widthFraction),
heightDimension: .estimated(50))
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: size, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)
section.boundarySupplementaryItems = [sectionHeader]
return section
}
That should work.

UICollectionViewCompositionalLayout - Unexpected layout with fractionalWidth width and self sizing cells

Requirement
I am trying to make a CollectionView section that has 1 large item then 4 half-width items below like this:
Each cell will contain labels that support multiline text or variable length with dynamic type support.
Approach
I have written the following compositional layout code which constructs 3 groups. A fullWidthGroup contains a single item that is full width (item 1). A halfWidthGroup will hold 2 items per "row" and a main outer group group which is made up of 1 fullWidthGroup and 2 halfWidthGroups.
private let compositionalLayout = UICollectionViewCompositionalLayout(sectionProvider: { (sectionIndex, environment) -> NSCollectionLayoutSection? in
let margin: CGFloat = 8
// Items
let fullWidthItemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(50))
let fullWidthItem = NSCollectionLayoutItem(layoutSize: fullWidthItemSize)
fullWidthItem.contentInsets = .init(top: 0, leading: margin, bottom: 0, trailing: margin)
let halfWidthItemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .estimated(50))
let halfWidthItem = NSCollectionLayoutItem(layoutSize: halfWidthItemSize)
halfWidthItem.contentInsets = .init(top: 0, leading: margin, bottom: 0, trailing: margin)
// Groups
let fullWidthGroup = NSCollectionLayoutGroup.horizontal(layoutSize: fullWidthItemSize, subitems: [fullWidthItem])
let halfWidthGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: halfWidthItemSize.heightDimension)
let halfWidthGroup = NSCollectionLayoutGroup.horizontal(layoutSize: halfWidthGroupSize, subitems: [halfWidthItem])
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(150))
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [fullWidthGroup, halfWidthGroup, halfWidthGroup])
group.interItemSpacing = .fixed(margin)
// Section
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = margin
return section
})
This gives the desired result for basic cells that do not contain any constraints (to self size them).
Problem
As soon as I create a cell that has constraints that can be used to correctly evaluate the size of the cell width of the cells is unexpectedly changed.
Resulting in the following layout.
Expectation
I would expect the last 4 items to take up half the width but instead, they appear to be taking up less space. Item 1 which should be full width with an 8 point margin on each side seems to also be sized incorrectly with ~16 point margin on the trailing edge.
Full Code
class ViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
private let compositionalLayout = UICollectionViewCompositionalLayout(sectionProvider: { (sectionIndex, environment) -> NSCollectionLayoutSection? in
let margin: CGFloat = 8
// Items
let fullWidthItemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(50))
let fullWidthItem = NSCollectionLayoutItem(layoutSize: fullWidthItemSize)
fullWidthItem.contentInsets = .init(top: 0, leading: margin, bottom: 0, trailing: margin)
let halfWidthItemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .estimated(50))
let halfWidthItem = NSCollectionLayoutItem(layoutSize: halfWidthItemSize)
halfWidthItem.contentInsets = .init(top: 0, leading: margin, bottom: 0, trailing: margin)
// Groups
let fullWidthGroup = NSCollectionLayoutGroup.horizontal(layoutSize: fullWidthItemSize, subitems: [fullWidthItem])
let halfWidthGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: halfWidthItemSize.heightDimension)
let halfWidthGroup = NSCollectionLayoutGroup.horizontal(layoutSize: halfWidthGroupSize, subitems: [halfWidthItem])
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(150))
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [fullWidthGroup, halfWidthGroup, halfWidthGroup])
group.interItemSpacing = .fixed(margin)
// Section
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = margin
return section
})
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(UINib(nibName: "BasicCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "Cell")
collectionView.collectionViewLayout = compositionalLayout
collectionView.dataSource = self
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
cell.contentView.backgroundColor = .red
return cell
}
}
The BasicCollectionViewCell is created in a nib and contains a stack view (added to the contentView). The StackView is pinned to all edges with a priority of 1000. Inside the stackview is a label.

How can I offset a Compositional Layout with an image / icon / view like this

I am trying to achieve a 'tag cloud' effect which should look something like this -
Each Tag Item X is a cell and the # can either be an image, a cell or a view. It just needs to these are hash tags.
My current attempt at a tag cloud looks like this -
I cannot work out how to offset the section items and insert a view or any kind into that space.
I did try a hack or sorts, in that each cell contained the icon and a label, I then hide the icon on every cell after the first. This did not work however as Tag Item 3 would wrap underneath the icon and there were some re use issues also.
How can I achieve this UI please?
I believe I may need to render a nested group, 1 cell in the first with the icon and mu tags in the trailing group? I cannot make this work though.
import UIKit
final class CustomCell: UICollectionViewCell {
let label = UILabel(frame: .zero)
override init(frame: CGRect) {
super.init(frame: frame)
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .clear
label.font = .systemFont(ofSize: 16)
label.textColor = .white
label.textAlignment = .center
label.sizeToFit()
contentView.addSubview(label)
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8),
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8)
])
}
required init?(coder: NSCoder) {
return nil
}
}
protocol SectionData {
var text: String { get }
}
struct DummyData: SectionData {
let text: String
}
enum SectionType: Int, CaseIterable {
case single
case double
case carousel
case tags
}
struct Section {
let id: Int
let type: SectionType
let title: String?
let subtitle: String?
let data: [SectionData]
}
class ViewController: UIViewController {
private var items: [Section] = [] {
didSet { collectionView.reloadData() }
}
private(set) lazy var collectionView: UICollectionView = {
let collectionView = UICollectionView(frame: view.frame, collectionViewLayout: makeLayout())
collectionView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
collectionView.backgroundColor = .systemBackground
collectionView.dataSource = self
collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "CustomCell")
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
items = [
Section(id: 0, type: .single, title: nil, subtitle: nil, data: Array(0...3).map { index in DummyData(text: "List Item \(index)") }),
Section(id: 1, type: .carousel, title: nil, subtitle: nil, data: Array(0...6).map { index in DummyData(text: "Carousel Item \(index)") }),
Section(id: 2, type: .tags, title: nil, subtitle: nil, data: Array(0...15).map { index in DummyData(text: "Tag Item \(index)") })
]
}
}
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return items.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items[section].data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let model = items[indexPath.section].data[indexPath.item]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.label.text = model.text
cell.label.sizeToFit()
cell.backgroundColor = indexPath.item % 2 == 0 ? .darkGray : .lightGray
return cell
}
}
extension ViewController {
func makeLayout() -> UICollectionViewLayout {
let layout = UICollectionViewCompositionalLayout { [weak self] index, env in
guard let self = self else { return nil }
let section = self.items[index]
switch section.type {
case .single: return self.makeSingleSection()
case .carousel: return self.makeCarouselSection()
case .tags: return self.makeTagSection()
default: return nil
}
}
let config = UICollectionViewCompositionalLayoutConfiguration()
config.interSectionSpacing = 20
layout.configuration = config
return layout
}
func makeSingleSection() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
let layoutGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(100))
let layoutGroup = NSCollectionLayoutGroup.horizontal(layoutSize: layoutGroupSize, subitems: [layoutItem])
layoutGroup.interItemSpacing = .fixed(12)
let layoutSection = NSCollectionLayoutSection(group: layoutGroup)
layoutSection.contentInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 16)
layoutSection.interGroupSpacing = 8
return layoutSection
}
func makeCarouselSection() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
layoutItem.contentInsets = .init(top: 0, leading: 8, bottom: 0, trailing: 8)
let layoutGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.83), heightDimension: .estimated(350))
let layoutGroup = NSCollectionLayoutGroup.horizontal(layoutSize: layoutGroupSize, subitems: [layoutItem])
let layoutSection = NSCollectionLayoutSection(group: layoutGroup)
layoutSection.orthogonalScrollingBehavior = .groupPaging
layoutSection.contentInsets = .init(top: 0, leading: 8, bottom: 0, trailing: 24)
return layoutSection
}
func makeTagSection() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(widthDimension: .estimated(100), heightDimension: .absolute(36))
let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
let layoutGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: itemSize.heightDimension)
let layoutGroup = NSCollectionLayoutGroup.horizontal(layoutSize: layoutGroupSize, subitems: [layoutItem])
layoutGroup.interItemSpacing = .fixed(8)
let layoutSection = NSCollectionLayoutSection(group: layoutGroup)
layoutSection.contentInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 16)
layoutSection.interGroupSpacing = 8
return layoutSection
}
}
I would use a NSCollectionLayoutBoundarySupplementaryItem on the leading edge.
You should be able to position it correctly using the absoluteOffset value.
func makeTagSection() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(widthDimension: .estimated(100), heightDimension: .absolute(36))
let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
let layoutGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: itemSize.heightDimension)
let layoutGroup = NSCollectionLayoutGroup.horizontal(layoutSize: layoutGroupSize, subitems: [layoutItem])
layoutGroup.interItemSpacing = .fixed(8)
layoutGroup.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: .fixed(44), top: nil, trailing: nil, bottom: nil)
let leftSize = NSCollectionLayoutSize(widthDimension: .absolute(36), heightDimension: .absolute(36))
let left = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: leftSize, elementKind: ViewController.leadingKind, alignment: .topLeading, absoluteOffset: .init(x: 0, y: 36))
let layoutSection = NSCollectionLayoutSection(group: layoutGroup)
layoutSection.contentInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 16)
layoutSection.interGroupSpacing = 8
layoutSection.boundarySupplementaryItems = [left]
return layoutSection
}
You can simply create a UICollectionReusableView to represent your icon.

Resources