Apparently Hello, this year can not be easily held a year-end party is sad TOSH. Today, I will be in charge of the 17th day of the ZOZO Technologies Advent Calendar!
By the way, every iOS engineer uses CollectionView. However, as you all feel, the UI of apps is becoming more and more complicated than it is.
The following example
AbemaTV | RakutenNBA | UberEats | AppleStore |
---|---|---|---|
From an engineer's point of view, it is difficult to implement these by first putting the whole in TableView, putting the CollectionView in the Cell, and then Layout the whole so that it becomes Horizontal in it. And above all, it is difficult to nest the contents more and more. Apple is also aware of that, and at WWDC19, we have announced a new Compositional Layout. However, it is compatible with iOS 13 or later. For business, the option to turn off iOS 12 is quite difficult, and in the end, it will be implemented by brute force. Good news for all of you! Our technical advisor, Mr. Kishikawa, has created a library that allows you to use CompositionLayout on iOS12! https://github.com/kishikawakatsumi/IBPCollectionViewCompositionalLayout So, in this article, I would like to introduce what kind of design should be created to make it easier to use in actual operation ~
By the way, CookPad Introduction is the method to stop the banner used in the App Store in the center, but it is quite difficult. .. ..
--People who use Collection View on a regular basis --People looking for the best design --People who are new to Compositional Layout
Please refer to here for details. https://developer.apple.com/documentation/uikit/views_and_controls/collection_views/layouts
Especially, I think that the image of putting UIcollectionViewCell on a custom item is good.
Now let's actually implement it.
First, create a Protocol for the section.
Section.swift
protocol Section {
//Number of items in each section
var numberOfItems: Int { get }
//What to do when an item in each section is tapped
//If you set it in the closure, you can add processing from the VC side.
var didSelectItem: ((Int) -> Void)? { get }
//Here, we will actually build the layout
func layoutSection() -> NSCollectionLayoutSection
//Set the Cell to be used as an Item here.
func configureCell(collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell
}
On the VC side, it is an image that uses the Section array created earlier.
ViewController.swift
import UIKit
final class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
}
@IBOutlet weak var collectionView: UICollectionView!
//Make the layout of the section set here
private var collectionViewLayout: UICollectionViewLayout {
let sections = self.sections
let layout = UICollectionViewCompositionalLayout { (sectionIndex, environment) -> NSCollectionLayoutSection? in
return sections[sectionIndex].layoutSection()
}
return layout
}
private var sections: [Section] = [] {
didSet {
//Update the layout when sections are updated
collectionView.collectionViewLayout = collectionViewLayout
collectionView.reloadData()
}
}
}
//dataSource settings
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return sections.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return sections[section].numberOfItems
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return sections[indexPath.section].configureCell(collectionView: collectionView, indexPath: indexPath)
}
}
//delegate settings
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let didSelectItem = sections[indexPath.section].didSelectItem else {
return
}
didSelectItem(indexPath.row)
}
}
This time, we will proceed on the premise that the cell for Item has been created in advance.
ItemsSection.swift
//Inherit Section
struct ItemsSection: Section {
var didSelectItem: ((Int) -> Void)?
private var items: [Items] = []
var numberOfItems: Int {
self.items.count
}
func layoutSection() -> NSCollectionLayoutSection {
//Layout settings for Item
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
//Layout settings for group
let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(UIScreen.main.bounds.width - 40), heightDimension: .absolute(184))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
//Layout settings for Section
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = 10
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 20)
//Set whether to stop scrolling or not here
section.orthogonalScrollingBehavior = .groupPaging
return section
}
func configureCell(collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ItemCollectionViewCell.self), for: indexPath) as! ItemCollectionViewCell
//Set the Cell here
return cell
}
}
extension BannerSection {
//If you need an Initializer, it's a good idea to cut it out into an Extension.
init() {
}
}
It's a good idea to create one section struct for each section.
Now let's add the Section we created earlier.
ViewController.swift
func viewDidLoad() {
~~abridgement~~
collectionView.register(ItemCollectionViewCell.self,
forCellWithReuseIdentifier: String(describing: ItemCollectionViewCell.self))
var itemsSection = ItemsSection()
itemsSection.didSelectItem = { index in
//Here, the process at the time of cell selection is performed.
}
sections.append(itemsSection)
}
You have now created a CollectionView that sucrose sideways and stops in the center.
If you use these well, you can also create a layout like the image below! (Sorry, I don't have time and it will be the image in the figure Sweat) The numbers in [] are [Section number, row number]. The important merit of this is that you can manage everything on one CollectionView without nesting like CollectionView in TableView! Even with complex layouts, you can design in a manageable way!
I want to add Header and Footer to each section! I think there are many people who say that. There are two main ways to create a Header and Footer.
I think these methods have advantages and disadvantages, but if you want to add complex touch events to Header and Footer, method 2 is good, especially if you do not use touch events or tap to make an accordion. If you only want to process, I think method 1 is easier.
Add another method to the previous Section.
Section.swift
Protocol Section {
~~abridgement~~
//UICollectionReusableView if you don't use Header or Footer()return it
func configureHeaderFooter(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView
}
Next, let's add the newly added method to ItemsSection!
ItemsSection.swift
struct ItemsSection: Section {
func layoutSection() -> NSCollectionLayoutSection {
~~abridgement~~
// header
let headerSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(95))
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: headerSize,
elementKind: "section-header-element-kind",
alignment: .top)
// footer
let footerSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(45))
let sectionFooter = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: footerSize,
elementKind: "section-footer-element-kind",
alignment: .bottom)
// header,Add footer
section.boundarySupplementaryItems = [sectionHeader, sectionFooter]
~~abridgement~~
}
func configureHeaderFooter(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
//The character string here is fixed
switch kind {
// header
case "section-header-element-kind":
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: String(describing: ItemHeaderCell.self), for: indexPath) as! ItemHeaderCell
//Header setup
return header
// footer
case "section-footer-element-kind":
let footer = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: String(describing: ItemFooterCell.self), for: indexPath) as! ItemFooterCell
//Footer setup
return footer
default:
//UICollectionReusableView if Header and Footer are not set()return it
return UICollectionReusableView()
}
}
}
Finally, the VC side
ViewController.swift
final class ViewController: UIViewController {
override func viewDidLoad() {
~~abridgement~~
collectionView.register(itemHeaderCell.self, forSupplementaryViewOfKind: "section-header-element-kind", withReuseIdentifier: String(describing: itemHeaderCell.self))
collectionView.register(itemFooterCell.self, forSupplementaryViewOfKind: "section-footer-element-kind", withReuseIdentifier: String(describing: itemFooterCell.self))
~~abridgement~~
}
}
extension ViewController: UIViewControllerDataSource {
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let view = sections[indexPath.section].configureHeaderFooter(collectionView, viewForSupplementaryElementOfKind: kind, at: indexPath)
//If you want to add a tap gesture to the view, do it here
return view
}
}
With this kind of feeling, if you implement it separately for ViewController and Section, it will be a pretty good design, isn't it? If you use the above-mentioned library, you can use CompositionalLayout from iOS12, so please start using it little by little ~ By the way, you can easily migrate even after the end of support for iOS 12.
This time, I used a normal DataSource, but at the same time as the CompositionLayout, a DiffrableDataSource has also appeared, so if you combine it with this, you can make another design? I will write about DiffrableDataSource again in the next article! Well then!
Recommended Posts