iOS 14使用最新的CollectionView(原文翻译)1-12之CompositionalLayout布局

将合成布局带到你的应用程序中,并使用不同的数据源简化用户界面的更新。

概述

集合视图允许您以灵活的视觉排列方式呈现一组数据。此示例应用程序演示如何在集合视图中创建各种类型的布局和管理数据。本文重点介绍两项关键技术:

  • CompositionalLayout (组合布局),一种集合视图布局,它是可组合的、灵活的和快速的,允许您为内容构建任何类型的视觉安排。
  • DiffableDataSource(可扩散数据源)是一种特殊类型的数据源,它提供了简单高效地管理集合视图数据和用户界面更新所需的行为。

1. 创建网格布局

网格示例演示如何通过使用分数大小来创建网格布局,以使五个大小相等的项组成一行。它创建一个水平组,其中每个项的宽度为组宽度的20%,方法是使用.fractionalWidth(0.2)。每行五个项目在一个节中重复多次以创建网格。

let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.2),
                                     heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)

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
创建网格布局

2. 在项目周围添加间距

基于上个例子中的布局构建,显示如何使用contentInsets属性在项目周围添加间距。在这里,此属性应用于每个项目边缘周围的均匀间距。

let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.2),
                                     heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)
添加间距

3. 创建列布局

三列网格示例演示如何创建三列布局,方法是使用count参数 horizontal(layoutSize:subitem:count:) 中指定的确切项数创建一个组。这种方法简化了精确指定一个组包含多少项的过程。在这种情况下,count参数优先于itemSize,并且自动计算项大小以适合指定的项数。

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: 3)
let spacing = CGFloat(10)
group.interItemSpacing = .fixed(spacing)
三列布局

4. 显示每个Section的不同布局

不同的Sections示例演示如何在同一集合视图布局的不同部分中显示不同的布局安排。
创建具有不同Section布局需要具有Section提供程序的组合布局。Section提供程序中的代码访问Section的索引(sectionIndex)以确定它配置的Section,并为每个Section显示不同的布局。

let layout = UICollectionViewCompositionalLayout { (sectionIndex: Int,
    layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in

    guard let sectionLayoutKind = SectionLayoutKind(rawValue: sectionIndex) else { return nil }
    let columns = sectionLayoutKind.columnCount

    // The group auto-calculates the actual item width to make
    // the requested number of columns fit, so this widthDimension is ignored.
    let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                         heightDimension: .fractionalHeight(1.0))
    let item = NSCollectionLayoutItem(layoutSize: itemSize)
    item.contentInsets = NSDirectionalEdgeInsets(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 = NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20)
    return section
}
return layout
不同Section的布局

5. 在不同的环境中显示不同的布局

“自适应Section”示例演示如何创建一个布局,使其适应所显示的环境。在本例中,显示的列数根据可用屏幕大小而变化。创建适应新环境的布局需要具有Section提供程序的合成布局。Section提供程序中的代码访问当前布局中的可用空间量(layoutEnvironment.container.effectiveContentSize),并根据可用宽度显示不同的列数。

let layout = UICollectionViewCompositionalLayout {
    (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
    guard let layoutKind = SectionLayoutKind(rawValue: sectionIndex) else { return nil }

    let columns = layoutKind.columnCount(for: layoutEnvironment.container.effectiveContentSize.width)

    let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.2),
                                         heightDimension: .fractionalHeight(1.0))
    let item = NSCollectionLayoutItem(layoutSize: itemSize)
    item.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)

    let groupHeight = layoutKind == .list ?
        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 = NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20)
    return section
}
return layout
自适应Section

6. 为Item添加标记

Item Badges示例演示如何将诸如Badges之类的补充视图,添加到collection view中的items上。它通过为标记创建一个辅助项,并在创建项本身时传入该辅助项,来创建标记并将其添加到每个items的右上角。数据源data source在其supplementaryViewProvider中配置每个标记。

let badgeAnchor = NSCollectionLayoutAnchor(edges: [.top, .trailing], fractionalOffset: CGPoint(x: 0.3, y: -0.3))
let badgeSize = NSCollectionLayoutSize(widthDimension: .absolute(20),
                                      heightDimension: .absolute(20))
let badge = NSCollectionLayoutSupplementaryItem(
    layoutSize: badgeSize,
    elementKind: ItemBadgeSupplementaryViewController.badgeElementKind,
    containerAnchor: badgeAnchor)

let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.25),
                                     heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize, supplementaryItems: [badge])
item.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)
为Item添加标记

7. 向Sections添加Headers页眉和Footers页脚

Sections Header/Footer 示例显示如何向collection view的每个Section添加页眉和页脚。它创建边界补充项来表示页眉和页脚,并将它们设置为节的边界补充项(boundarySupplementaryItems)。

let headerFooterSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                             heightDimension: .estimated(44))
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
    layoutSize: headerFooterSize,
    elementKind: SectionHeadersFootersViewController.sectionHeaderElementKind, alignment: .top)
let sectionFooter = NSCollectionLayoutBoundarySupplementaryItem(
    layoutSize: headerFooterSize,
    elementKind: SectionHeadersFootersViewController.sectionFooterElementKind, alignment: .bottom)
section.boundarySupplementaryItems = [sectionHeader, sectionFooter]

该示例使用页眉和页脚的补充注册来配置它们的内容和外观。

let headerRegistration = UICollectionView.SupplementaryRegistration
(elementKind: SectionHeadersFootersViewController.sectionHeaderElementKind) {
    (supplementaryView, string, indexPath) in
    supplementaryView.label.text = "\(string) for section \(indexPath.section)"
    supplementaryView.backgroundColor = .lightGray
    supplementaryView.layer.borderColor = UIColor.black.cgColor
    supplementaryView.layer.borderWidth = 1.0
}

collection view使用这些补充注册将diffable数据源的supplementaryViewProvider中配置的页眉和页脚出列。

dataSource.supplementaryViewProvider = { (view, kind, index) in
    return self.collectionView.dequeueConfiguredReusableSupplementary(
        using: kind == SectionHeadersFootersViewController.sectionHeaderElementKind ? headerRegistration : footerRegistration, for: index)
}
添加页眉和页脚

8. 将Section Header固定到Section

固定 Section Headers示例显示如何将Section Header固定到其Section。这样Header在它所附加到的Section滚动任何部分期间,都是可见的,并且Footer保持不变。此示例将Header的pinToVisibleBounds属性设置为true,并将其zIndex增加到大于1的值,以便在滚动期间Header显示在Section的顶部。

let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
    layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                      heightDimension: .estimated(44)),
    elementKind: PinnedSectionHeaderFooterViewController.sectionHeaderElementKind,
    alignment: .top)
let sectionFooter = NSCollectionLayoutBoundarySupplementaryItem(
    layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                      heightDimension: .estimated(44)),
    elementKind: PinnedSectionHeaderFooterViewController.sectionFooterElementKind,
    alignment: .bottom)
sectionHeader.pinToVisibleBounds = true
sectionHeader.zIndex = 2
section.boundarySupplementaryItems = [sectionHeader, sectionFooter]

collection view使用这些补充注册将diffable数据源的supplementaryViewProvider中配置的页眉和页脚出列。

dataSource.supplementaryViewProvider = { (view, kind, index) in
    return self.collectionView.dequeueConfiguredReusableSupplementary(
        using: kind == PinnedSectionHeaderFooterViewController.sectionHeaderElementKind ? headerRegistration : footerRegistration, for: index)
}
Header固定到Section

9. 用背景装饰Sections

Section背景装饰示例演示如何通过添加Section背景来区分Section。它通过使用background(elementKind:)创建装饰项来创建Section背景。然后将该装饰项设置为该Section的decorationItems属性。

let sectionBackgroundDecoration = NSCollectionLayoutDecorationItem.background(
    elementKind: SectionDecorationViewController.sectionBackgroundDecorationElementKind)
sectionBackgroundDecoration.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)
section.decorationItems = [sectionBackgroundDecoration]

然后,下面的代码使用register(_:forDecorationViewOfKind:) 将背景视图注册到布局中。

let layout = UICollectionViewCompositionalLayout(section: section)
layout.register(
    SectionBackgroundDecorationView.self,
    forDecorationViewOfKind: SectionDecorationViewController.sectionBackgroundDecorationElementKind)
return layout
背景装饰Sections

10. 通过嵌套Groups创建自定义布局

嵌套Groups示例演示如何通过将Group嵌套在其他Group中来创建灵活的布局安排。它创建一个包含两个item的垂直Group,并将垂直Group与水平父Group中的一个item组合。

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 nestedGroup = NSCollectionLayoutGroup.horizontal(
    layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                      heightDimension: .fractionalHeight(0.4)),
    subitems: [leadingItem, trailingGroup])
嵌套Groups创建自定义布局

11. 水平滚动部分

垂直section示例演示如何创建在垂直滚动布局中水平滚动的section。将section的orthogonalScrollingBehavior属性设置为.none以外的值将导致section垂直于主布局轴布局其内容。在这种情况下,由于默认情况下布局垂直滚动,因此section水平滚动。

section.orthogonalScrollingBehavior = .continuous
水平滚动

12. 选择水平滚动和分页行为

垂直部分行为示例显示UICollectionLayoutSectionOrthogonalScrollingBehavior的每个选项。布局的每个部分都演示了不同的正交滚动行为,显示了滚动和分页选项之间的差异。在这种情况下,因为默认情况下布局是垂直滚动的,所以section本身是水平滚动的。

case none
该节不允许用户垂直滚动其内容。
case continuous
该部分允许用户连续垂直滚动其内容。


continuous

case continuousGroupLeadingBoundary
该部分允许用户垂直滚动其内容,在可见组的前边界处自然停止。


continuousGroupLeadingBoundary

case paging
该部分允许用户以垂直方式分页其内容。
paging

case groupPaging
该部分允许用户一次一组地垂直翻页其内容。
groupPaging

case groupPagingCentered
该部分允许用户一次对一组内容进行垂直分页,使每组居中。


groupPagingCentered

你可能感兴趣的:(iOS 14使用最新的CollectionView(原文翻译)1-12之CompositionalLayout布局)