Disposition de composition sur iOS 13. Les bases

Bonne après-midi,


Dans la pratique, iOS, le développeur est souvent confronté à la tâche d'afficher une grande quantité d'informations sous la forme d'une liste ou d'une collection, en règle générale, UITableViewou sont parfaits pour cela UICollectionView. La tâche de réaliser un écran, qui est une combinaison d'une liste et d'une collection, est également souvent rencontrée.


Dans cet article, nous examinerons quelles nouvelles fonctionnalités iOS 13 a apportées pour implémenter cette tâche.



introduction


, , , , AppStore.



?


iOS 13,


  • ,
  • ,
  • ,
  • UIScrollView

.


  • self-sizing
  • iPad iPhone

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
    }

Nested Groups Scrolling


? ! , , .


// ...
section.orthogonalScrollingBehavior = .continuous
// ...



iOS 13 UICollectionView, Compositional Layout .
. .


:



All Articles