Buenas tardes,
En la práctica, iOS, el desarrollador a menudo se enfrenta a la tarea de mostrar una gran cantidad de información en forma de una lista o como una colección, por regla general, UITableViewo son excelentes para esto UICollectionView. También a menudo se encuentra la tarea de realizar una pantalla, que es una combinación de una lista y una colección.
En este artículo, consideraremos qué nuevas características trajo iOS 13 para implementar esta tarea.

Introducción
, , , , 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 .
. .
: