Guten Tag,
In der Praxis steht der Entwickler unter iOS häufig vor der Aufgabe, eine große Menge an Informationen in Form einer Liste oder in der Regel als Sammlung anzuzeigen, UITableView
oder eignet sich hervorragend dafür UICollectionView
. Ebenfalls häufig anzutreffen ist die Aufgabe, einen Bildschirm zu realisieren, der eine Kombination aus einer Liste und einer Sammlung darstellt.
In diesem Artikel werden wir untersuchen, welche neuen Funktionen iOS 13 zur Implementierung dieser Aufgabe gebracht hat.

Einführung
, , , , AppStore.

?
iOS 13,
.
iOS 12.
Compositional Layout
iOS 13 Apple , — Compositional Layout, 3- — , .
— , , , , , , , .

Compositional Layout — , , .
— , , - . , .
— grid-, flow- , .
— , , .
Compositional Layout. 4 :
NSCollectionLayoutSize
— ;NSCollectionLayoutItem
— ;NSCollectionLayoutGroup
— , ;NSCollectionLayoutSection
— ;UICollectionViewCompositionalLayout
— .
UICollectionViewCompositionalLayout
.
class UICollectionViewCompositionalLayout : UICollectionViewLayout { ... }
UICollectionViewCompositionalLayout
UICollectionViewLayout
, UICollectionView
.
List Layout
. storyboard-, UIViewController
, viewDidLoad()
UICollectionView
init(frame:, layout:)
UICollectionViewCompositionalLayout
. , UICollectionViewCompositionalLayout
.
, . , , , .

.
private func createLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .absolute(44))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
private func configureHierarchy() {
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createLayout())
}
itemSize
, , NSCollectionLayoutDimension
. .
fractionalWidth(_:)
, , 0 1 , , 0.5
— () ;fractionalHeight(_: )
, , 0 1 , , 0.5
— () ;absolute(_: )
, , , 44.0
;estimated(_:)
, , .
itemSize
widthDimension = .fractionalWidth(1.0)
, heightDimension = .fractionalHeight(1.0)
, , . .
, widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(44)
, , , 44.0
.
:
horizontal(layoutSize: , subitem: , count: )
, , ;horizontal(layoutSize:, subitems: )
, , , ;vertical(layoutSize:, subitem:, count:)
, , ;vertical(layoutSize:, subitems: )
, , , ;custom(layoutSize:, itemProvider: )
, , .
, .horizontal
.vertical
, , , .custom
, , .
NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
, , .
NSCollectionLayoutSection(group: group), . Compositional Layout
.
, , . ?
Grid Layout

, ? , , , .
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0/4.0),
heightDimension: .fractionalHeight(1.0))
, 4 , .

Two Column Layout
, , , , , widthDimension
.

.
private func createLayout() -> UICollectionViewLayout {
let spacing: CGFloat = 10
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .absolute(44))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 2)
group.interItemSpacing = .fixed(spacing)
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = .init(top: spacing, leading: spacing, bottom: spacing, trailing: spacing)
section.interGroupSpacing = spacing
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
group.interItemSpacing = .fixed(spacing)
, section.interGroupSpacing = spacing
, contentInset
section.contentInsets = .init(top: spacing, leading: spacing, bottom: spacing, trailing: spacing)
. spacing
.
, interItemSpacing
, interGroupSpacing
, NSCollectionLayoutSpacing
.
class NSCollectionLayoutSpacing : NSObject, NSCopying {
class func fixed(_ fixedSpacing: CGFloat) -> Self // i.e. ==
class func flexible(_ flexibleSpacing: CGFloat) -> Self // i.e. >=
// ...
}
func fixed(_ fixedSpacing: )
, ;func flexible(_ flexibleSpacing:)
, , , ;
Inset Items Grid Layout
, , ? , , . , 5 , , 1.0/5.0 , . , 1.0/5.0 . , .
, .contentInsets
NSCollectionLayoutItem
.

.
private func createLayout() -> UICollectionViewLayout {
let spacing: CGFloat = 10
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(0.2),
heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = .init(top: spacing, leading: spacing, bottom: spacing, trailing: spacing)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(0.2))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
Distinct Sections Layout
, .
UICollectionViewCompositionalLayout
, UICollectionViewCompositionalLayoutSectionProvider
.
typealias UICollectionViewCompositionalLayoutSectionProvider = (Int, NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection?
class UICollectionViewCompositionalLayout : UICollectionViewLayout {
init(section: NSCollectionLayoutSection)
init(sectionProvider: @escaping UICollectionViewCompositionalLayoutSectionProvider)
}
UICollectionViewCompositionalLayoutSectionProvider
2 , , , .
3 , , grid- 3- , grid-, 5- .

.
enum Section: Int, CaseIterable {
case list
case grid3
case grid5
var columnCount: Int {
switch self {
case .list:
return 1
case .grid3:
return 3
case .grid5:
return 5
}
}
}
private func createLayout() -> UICollectionViewLayout {
let layout = UICollectionViewCompositionalLayout { (sectionIndex, layoutEnvironment) -> NSCollectionLayoutSection? in
guard let sectionKind = Section(rawValue: sectionIndex) else { return nil }
let columns = sectionKind.columnCount
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = .init(top: 2, leading: 2, bottom: 2, trailing: 2)
let groupHeight = columns == 1 ?
NSCollectionLayoutDimension.absolute(44) :
NSCollectionLayoutDimension.fractionalWidth(0.2)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: groupHeight)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: columns)
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = .init(top: 20, leading: 20, bottom: 20, trailing: 20)
return section
}
return layout
}
Adaptive Sections Layout
, NSCollectionLayoutEnvironment
, traitCollection
.
protocol NSCollectionLayoutEnvironment : NSObjectProtocol {
var container: NSCollectionLayoutContainer { get }
var traitCollection: UITraitCollection { get }
}
ompact size-class , , , , .
traitCollection.verticalSizeClass
layoutEnvironment
.
container.effectiveContentSize
layoutEnvironment
.

.

.
enum Section: Int, CaseIterable {
case list
case grid3
case grid5
func columnCount(for width: CGFloat) -> Int {
let wideMode = width > 800
switch self {
case .list:
return wideMode ? 2 : 1
case .grid3:
return wideMode ? 6 : 3
case .grid5:
return wideMode ? 10 : 5
}
}
}
private func createLayout() -> UICollectionViewLayout {
let layout = UICollectionViewCompositionalLayout { (sectionIndex, layoutEnvironment) -> NSCollectionLayoutSection? in
guard let sectionKind = Section(rawValue: sectionIndex) else { return nil }
let columns = sectionKind.columnCount(for: layoutEnvironment.container.effectiveContentSize.width)
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = .init(top: 2, leading: 2, bottom: 2, trailing: 2)
let groupHeight = layoutEnvironment.traitCollection.verticalSizeClass == .compact ?
NSCollectionLayoutDimension.absolute(44) :
NSCollectionLayoutDimension.fractionalWidth(0.2)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: groupHeight)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: columns)
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = .init(top: 20, leading: 20, bottom: 20, trailing: 20)
return section
}
return layout
}
Nested Groups
class NSCollectionLayoutGroup : NSCollectionLayoutItem, NSCopying { ... }
NSCollectionLayoutGroup
, , NSCollectionLayoutItem
, , , .

.
func createLayout() -> UICollectionViewLayout {
let layout = UICollectionViewCompositionalLayout {
(sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
let leadingItem = NSCollectionLayoutItem(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.7),
heightDimension: .fractionalHeight(1.0)))
leadingItem.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
let trailingItem = NSCollectionLayoutItem(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(0.3)))
trailingItem.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
let trailingGroup = NSCollectionLayoutGroup.vertical(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.3),
heightDimension: .fractionalHeight(1.0)),
subitem: trailingItem, count: 2)
let bottomNestedGroup = NSCollectionLayoutGroup.horizontal(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(0.6)),
subitems: [leadingItem, trailingGroup])
let topItem = NSCollectionLayoutItem(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(0.3)))
topItem.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
let nestedGroup = NSCollectionLayoutGroup.vertical(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(0.4)),
subitems: [topItem, bottomNestedGroup])
let section = NSCollectionLayoutSection(group: nestedGroup)
return section
}
return layout
}
? ! , , .
section.orthogonalScrollingBehavior = .continuous

iOS 13 UICollectionView
, Compositional Layout .
. .
: