邳州建设局网站,小型网站开发成本,wordpress显示多少页,wordpress 文章名本文字数#xff1a;8325字 预计阅读时间#xff1a;45分钟 01 Collection View Layout全详解 UICollectionView在iOS中是构建复杂布局的强大工具。iOS13中引入的 UICollectionViewCompositionalLayout为创建自定义布局提供了全新的可能性。本文将深入探讨Compositional Lay… 本文字数8325字 预计阅读时间45分钟 01 Collection View Layout全详解 UICollectionView在iOS中是构建复杂布局的强大工具。iOS13中引入的 UICollectionViewCompositionalLayout为创建自定义布局提供了全新的可能性。本文将深入探讨Compositional Layout的工作原理,以及如何利用它创建复杂的分组、嵌套布局和增强视图。无论您是刚开始学习Compositional Layout,还是想掌握它的高级用法,本文都将是您的完美指南。让我们开始这个令人兴奋的布局之旅吧! 本文所有代码示例见https://github.com/kingnight/UICollectionCompositionalLayout CollectionView Layout 由三个布局部分组成 图1 1、Item 它是层次结构中的最小单元代表您想要在屏幕上显示的单个数据块Item展示在Cell内部。 2、Group Item位于Group内Group可以将其项目按水平行、垂直列或自定义排列它是布局的基本单位Group指定数据布局的方向并且可以组合在一起创建更复杂的布局。 3、Section Section只是一组数据对应于数据在数据源中的组织方式Collection View可以有多个Section每个Section包含自己的Group和Item。 在iOS 6中包含UICollectionView的API可以被划分为三个不同的类别——Data、Layout和Presentation。这种区别是UICollectionView如此灵活的核心。当UICollectionView在iOS 6中首次发布时数据是通过基于索引路径的协议UICollectionViewDataSource来管理的。对于布局有一个抽象类UICollectionViewLayout一个具体的子类UICollectionViewFlowLayout。在展示方面我们发布了两种视图类型UICollectionViewCell和UICollectionReusableView。 图2 在iOS13中我们分别为数据Data和布局Layout引入了两个新的组件分别是Diffable Data Source和Compositional Layout。这些API现在用于使用UICollectionViews构建应用程序。 图3 02 创建UICollectionViewLayout 1、布局关系 func configLayout() - UICollectionViewLayout {//Group width 1.0super view是sectionsection parent是collectionview layout所以它会占据整个屏幕的宽度height 0.2 *它的super view的宽度也是sectionlet 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、Item 这些Item不代表实际项目可以把它看作是提供实际数据时使用的蓝图它存储了UICollectionViewCell的布局信息NSCollectionLayoutSize允许我们定义宽度和高度作为NSCollectionLayoutDimension的一个实例Item → NSCollectionLayoutItem → NSCollectionLayoutSize → NSCollectionLayoutDimension。 1NSCollectionLayoutDimension 集合视图中的每个元素都有一个显式的宽度维度和高度维度它们组合起来定义了元素的大小(NSCollectionLayoutSize)可以使用绝对值、估计值或小数来表示Item的尺寸使用绝对值来指定精确的尺寸比如44 x 44的点正方形 let absoluteSize NSCollectionLayoutSize(widthDimension: .absolute(44),heightDimension: .absolute(44)) 如果内容的大小可能在运行时发生变化例如在加载数据或响应系统字体大小的变化时请使用估定值。您提供一个初始估计大小系统稍后计算实际值。 let estimatedSize NSCollectionLayoutSize(widthDimension: .estimated(200),heightDimension: .estimated(100)) 使用一个小数来定义一个相对于Item容器尺寸的值。此选项简化了指定长宽比。例如下面的元素的宽度和高度都等于其容器宽度的20%从而创建了一个随容器大小变化而变大或缩小的正方形。 let fractionalSize NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.2),heightDimension: .fractionalWidth(0.2)) 3、Groups Groups是NSCollectionLayoutGroup的实例必须指定坐标轴方向才能明确哪里做滚动、大小和它包含的Item我们将在后面讨论subitems: [item])在本例中Group表示行NSCollectionLayoutGroup.horizontal每个Group或row包含5个项目和Group的高度。等于单个元素的宽度Group是组合布局的基本单位。有三种形式水平垂直和自定义水平或垂直是两种基本形式还可以自定义Group允许你以自定义方式指定Item的绝对大小和位置。 4、Section 集合视图布局有一个或多个Section。section提供了一种将布局分成不同部分的方法每个Section可以与集合视图中的其他部分具有相同的布局也可以具有不同的布局。section的布局是由用来创建section的组(NSCollectionLayoutGroup)的属性决定的每个Section可以有自己的背景、页眉和页脚以区别于其他部分Section是NSCollectionLayoutSection的一个实例我们在这个Section下指定group后UICollectionViewCompositionalLayout我们传递Section。 图4 图5 5、Item Size计算 假设集合视图宽度 428屏幕宽度 428计算item size 你有一个Item想在屏幕上显示Section→Group→Item默认情况下section会填满整个屏幕宽度因为集合视图CollectionView的宽度占据了整个屏幕宽度对于section的高度section依赖于嵌套容器来通知它的大小Section包含多个GroupGroup宽度是相同的所以Group宽度将是428Group高 0.2 * Section宽度 85.7item宽度 0.2 *Group的宽度 85.7Item高度 1.0Group高度 85.7。 6、配置DataSource CollectionView没有组织和管理底层数据。这是数据源对象DataSource的职责Datasource管理数据、为CollectionView提供要显示的数据快照snapshot假设我们的数据由一系列名字组成我们想要在CollectionView中显示这些名字我们从初始快照开始快照被认为是当前UI状态的真相truth of the current UI state(这个非常重要)我们向数据源提供初始快照以及关于如何处理数据的指令它将处理其余的事情。 1snapshot 设集合有一个排序按钮我们按字母顺序对数据进行排序这是我们数据的第二个快照我们将它交还给数据源并要求它以不同的方式显示相同的数据数据源可以自动计算出两个快照之间的差异而不是简单地用新数据填充集合视图它可以告诉集合视图如何移动现有数据以我们想要的方式显示它因为数据源可以在两个快照之间有所不同所以它被称为diffable数据源Diffable数据源是一种声明式的方法来处理底层数据(而不是告诉数据源如何使用声明式方法移动数据我们只是简单地告诉数据源数据的新状态是什么)。数据源自动地完成工作找出旧状态和新状态之间的区别以及如何在集合视图中应用这些更改要做到这一点唯一的要求是在提供数据源的数据集中每个值必须有一个唯一的标识符。在这个简单的应用程序中我们只显示数字行你可以使用数字本身作为标识符只要每个数字的值不同它的值是唯一的。 图6 在代码中diffable数据源被定义为UICollectionViewDiffableDataSource类的实例。在创建实例时您指定它将包含什么类型的数据以及如何填充单元格的说明然后我们使用NSDiffableDataSourceSnapshot类创建数据的快照如图11所示我们使用UICollectionViewDiffableDataSource来创建datasource对象您需要将其维护为对datasource的强引用因此我们将其定义为存储属性UICollectionViewDiffableDataSource是一个泛型类它接受两个参数Section type和Item type如图10所示。SectionIdentifierType和ItemIdentifierType都有一个约束即它们都需要符合Hashable这些类型提供的哈希作为数据源的唯一标识符。这非常有用在实践中几乎任何对象都可以用来表示部分或项在我们的例子中我们只有一个section所以我们不需要任何复杂的东西我们可以使用字符串标识符来表示一个section但在swift中我们可以使用enum我们创建了名为section的enum来定义应用程序中的不同section我们添加了一个section case main来保存集合视图中的所有项目因为编译器将为枚举合成可哈希一致性这就是你需要做的一切对于Item由于应用程序在列表中显示数字您可以指定项目的类型为Int, Int已经符合Hashable所以我们不需要做任何额外的事情 -使用这两种类型enum和int我们可以创建一个UICollectionViewDiffableDataSource的实例我们为它提供集合视图引用以便它知道哪个集合视图工作第二个参数是闭包它定义了集合视图如何将数据映射到每个单元格闭包有三个参数集合视图对应单元格的indexpath以及要在单元格中显示的数据你应用的逻辑会在数据源中的每个数据实例上运行使用闭包你告诉数据源如何获取给定的数据项并在集合视图提供的单元格中显示(如cellForIndexPath)在closure中你可以像以前一样取出cell并配置cell。 图7 2Apply SnapShot to DataSource(The Truth) 您需要为DataSource提供的最后一件事是数据的初始快照SnapShotSnapShot是NSDiffableDataSourceSnapshot实例它是一个泛型类型有两个参数一个定义Section另一个定义Item类型var snapshot NSDiffableDataSourceSnapshotSection, Int()Snapshot根据Section定义其数据其中Section包含一系列Item创建快照首先要使用appendSections方法添加一个section这个方法接受一个section的数组该数组与数据源和快照类型定义中指定的类型匹配因为我们只有一个section你可以提供枚举值.main作为参数接下来需要向这个使用了snapshot.appendItems(#T##identifiers: [Int]##[Int]#, toSection: #T##Section?#)方法的区域中添加Item为每个会话指定Item因为我们只有一个Scetion所以无需指定现在我们指定需要应用到datasource的初始快照因为我们首先加载整个数据我们不需要动画。animatingDifferences(我们想用动画加载数据)apply的意思是日期源遍历数据并传递每个数字以及集合视图和每个单元格的indexpath到我们刚刚定义的闭包中。 图8 7、Spacing 图9 接下来看具体代码 Demo1演示 调整item的宽度占满整个屏幕类似UITableViewCell布局 图10 interGroupSpacing→图9所示部分中组之间的空间大小。由于目前每一行是一个Group每个Group里有一个Item。 图11 section contentInsets→section内容与其边界之间的空间大小。 NSDirectionalEdgeInsets→考虑了语言方向的边嵌入集。 图12 group.contentInsets →计算出元素的位置后元素内容周围用于调整最终大小的空间大小。内容嵌入到组中每个项目的每个边缘。所以你现在可以想我们可以有很多变化来添加边内嵌。 我们也可以在Item级别上指定注意顺序很重要如图13所示我们在创建group后指定项目内容内嵌在这种情况下它将不起作用对比图13 vs 14 图13 图14 在图13中可以看到我们创建了基于两列的布局。你应该看到的新东西是我们正在构建组它以稍微不同的方式表示每一行。我们使用了另一种形式的初始化器它接受显式的count参数这里我们显式地指定了每组或每行只有两个元素。现在这导致组合布局自动计算出元素的宽度必须是多少。我们在这里指定了元素的宽度因为我们总是必须。我们说的是容器的100%但是顶部的宽度值会被覆盖。当你要求每组元素的数量时组合布局将覆盖并计算满足我们请求实际需要的任何宽度。 图15 func configLayout() - 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, subitem:item,count: 2)let spacing CGFloat(10)group.interItemSpacing .fixed(spacing)group.contentInsets NSDirectionalEdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10)let section NSCollectionLayoutSection(group: group)section.interGroupSpacing spacingsection.contentInsets NSDirectionalEdgeInsets(top: 0, leading: 10, bottom: 20, trailing: 10)let layout UICollectionViewCompositionalLayout(section: section)return layout} 图16 03 定制Compositional Layout Demo2 1、定制Model与Cell class TopApp:UICollectionViewCell{IBOutlet weak var title: UILabel!IBOutlet weak var subTitle: UILabel!IBOutlet weak var appImageView: UIImageView!static let reuseIdentifier TopAppoverride class func awakeFromNib() {super.awakeFromNib()}
}struct TopAppModel:Hashable {let imageNamed:Stringlet title:Stringlet subTitle:Stringlet identifier UUID().uuidStringstatic var mock:[TopAppModel]{return [TopAppModel(imageNamed: externaldrive.connected.to.line.below, title: facebook title, subTitle: facebook subtitle),TopAppModel(imageNamed: personalhotspot.circle.fill, title: instagram title, subTitle: instagram subtitle),TopAppModel(imageNamed: bolt.horizontal.fill, title: linkedin title, subTitle: linkedin subtitle)]}
} 2、每个Section不同分布 之前我们针对所有section使用相同的布局即使用UICollectionViewCompositionalLayout(section: section)创建现在我们将创建每个section使用不同的布局当使用UICollectionDataSource协议时您可以使用IndexPath根据您所在的Section自定义单元格大小或Section行为我们可以在这里定义布局时也可以相似的根据Section的位置不同处理创建不同的布局layoutUICollectionViewCompositionalLayout可以使用sectionproviderUICollectionViewCompositionalLayoutSectionProvider来创建一个组合布局(UICollectionViewCompositionalLayout)它有多个具有不同布局的section。section提供程序跟踪它当前创建的section的索引因此你可以不同地配置每个section。typealias UICollectionViewCompositionalLayoutSectionProvider (Int, NSCollectionLayoutEnvironment) - NSCollectionLayoutSection?这里我们定义了一个接受closure的常量section提供程序传递给这个闭包的参数是section索引以及布局环境(该对象包含设置collection视图时关于布局环境的信息trait collection之类的属性和关于布局容器的内容大小等信息)您也可以基于此运行时信息自定义section布局 (见下面UICollectionViewCompositionalLayout定义)闭包返回NSCollectionLayoutSection类的实例这是你需要构建的因为我们有不同的布局我们创建了UICollectionViewCompositionalLayout实例与sectionprovider构造函数注意section provider是转义闭包所以要检查内存泄漏的问题layoutUICollectionViewCompositionalLayout- sectionproviderUICollectionViewCompositionalLayoutSectionProvider- sectionNSCollectionLayoutSection。 func createTopAppLayout() - NSCollectionLayoutSection {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(74))let group NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])let section NSCollectionLayoutSection(group: group)section.interGroupSpacing 10section.contentInsets NSDirectionalEdgeInsets(top: 10, leading: 0, bottom: 0, trailing: 0)return section}func createFeatureCellLayout() - NSCollectionLayoutSection {let itemSize NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.48), heightDimension: .fractionalHeight(1.0))let item NSCollectionLayoutItem(layoutSize: itemSize)let groupSize NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(300))let group NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])group.interItemSpacing .flexible(10)let section NSCollectionLayoutSection(group: group)section.interGroupSpacing 10section.contentInsets NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)return section}func configLayout2() - UICollectionViewLayout {let layout UICollectionViewCompositionalLayout { [weak self] sectionIndex, layoutEnvironment inif sectionIndex 0 {return self?.createTopAppLayout()}else if sectionIndex 1{return self?.createFeatureCellLayout()}else{return self?.createNestedGroupLayout()}}return layout} section不同布局 available(iOS 13.0, *)
MainActor open class UICollectionViewCompositionalLayout : UICollectionViewLayout {public init(section: NSCollectionLayoutSection)public init(section: NSCollectionLayoutSection, configuration: UICollectionViewCompositionalLayoutConfiguration)public init(sectionProvider: escaping UICollectionViewCompositionalLayoutSectionProvider)public init(sectionProvider: escaping UICollectionViewCompositionalLayoutSectionProvider, configuration: UICollectionViewCompositionalLayoutConfiguration)// Setting this property will invalidate the layout immediately to affect any changes// Note: any changes made to properties directly will have no effect.NSCopying open var configuration: UICollectionViewCompositionalLayoutConfiguration
}public typealias UICollectionViewCompositionalLayoutSectionProvider (Int, NSCollectionLayoutEnvironment) - NSCollectionLayoutSection? NSCollectionLayoutSpacing→一个定义集合视图中元素之间或元素周围空间的对象。在集合视图布局中你可以使用spacing对象来指定空间大小和计算空间大小的方式。可以使用fixed或flexible的间距来表示间距 使用fixed间距来提供精确数量的空间。例如下面的代码在组中的项之间创建10个点的间距使用flexible间距来提供最小的间距可以随着更多空间的可用性而增长。例如下面的代码在组中的项之间创建至少10个点的空间。当有更多可用空间时Item会在额外空间中均匀分布。 3、嵌套布局组Nested Layout Group compositional layout的核心就是布局layoutgroup实际上是NSCollectionLayoutItem的一个子类型class NSCollectionLayoutGroup : NSCollectionLayoutItem因为这个关系当你指定布局组中的Item时你可以嵌套Group。这种特殊的嵌套没有限制是任意的。因为我们有了这个它开启了许多有趣的新设计。 在下面这个例子中group由三个item组成在左侧有一个大Item右侧有一个垂直Group包含两个子Item。 首先我们创建一个左侧的大Item它将占其父元素的70%宽度它将占其父元素的100%高度我们为trailingItem创建布局数据它将占用其父元素宽度的100%和其父元素高度的30%第三我们创建了右侧垂直方向的Group并添加了我们在步骤2中创建的两个Item它将占用父元素宽度的30%并占用父元素高度的100%最后我们创建了水平的nestedGroup并添加了左侧和右侧组。nestedGroup将占用其父布局的100%宽度即section宽度section宽度最终将占用主布局的宽度即整个屏幕宽度nestedGroup将获取其父元素的100%高度即section的宽度并最终获取主布局的高度即整个屏幕的高度注意右侧组有两个元素对于子元素它充当父元素因此每个子元素将占用父元素的100%宽度它的屏幕宽度为0.3。 图17 Demo2三种前面已有布局组合展示 4、嵌套CollectionView Demo3-Nested 如下图所示我们创建的布局中item将占据整个屏幕宽度和高度是恒定的或绝对的其中重要的一点是orthogonalScrollingBehavior .continuous。 orthogonalScrollingBehavior:相对于主布局轴的部分滚动行为。 图18 - 实现水平滚动效果 在下图中可以看到我们注释掉了orthogonalScrollingBehavior因此它使用默认值none→该部分不允许用户正交滚动其内容。所以它垂直地添加item 图19- 注释掉了orthogonalScrollingBehavior垂直地添加item 如下面动图所示section.orthogonalScrollingBehavior .paging我们使用分页作为orthogonalScrollingBehavior的值它会进行一项一项的滚动。 图20 如下图所示我们创建了三个Item的嵌套组并创建了白色矩形来指定每个组因此通过使用。grouppaging当您滚动时您将看到另一个组简而言之总是滚动停止时组开始 图21 通过使用.grouppagingcentered当你滚动时它将进行组分页并将组放在屏幕的中心 图22 continuousGroupLeadingBoundary→该部分允许用户正交滚动其内容在可见组的前边自然停止continuous→该区域允许用户在连续滚动时垂直滚动内容none→该区域不允许用户垂直滚动其内容paging→允许用户正交地对其内容分页groupPaging→该节允许用户一次对一个组的内容进行正交分页groupPagingCentered→该部分允许用户一次对一个组的内容进行正交分页使每个组居中。 5、其他布局 测试1-类似AppStore的UI但是每一個高度都稍微不太一樣 图23 // 提供三種不同形狀的 item
let layoutSize NSCollectionLayoutSize(widthDimension: .absolute(110), heightDimension: .absolute(45))
let item NSCollectionLayoutItem(layoutSize: layoutSize)
let layoutSize2 NSCollectionLayoutSize(widthDimension: .absolute(110), heightDimension: .absolute(65))
let item2 NSCollectionLayoutItem(layoutSize: layoutSize2)
let layoutSize3 NSCollectionLayoutSize(widthDimension: .absolute(110), heightDimension: .absolute(85))
let item3 NSCollectionLayoutItem(layoutSize: layoutSize3)// 給剛好大小的 group
let groupLayoutSize NSCollectionLayoutSize(widthDimension: .absolute(110), heightDimension: .absolute(205)) //456585 195// 用 .vertical 指明我們的 group 是垂直排列的
let group NSCollectionLayoutGroup.vertical(layoutSize: groupLayoutSize, subitems: [item, item2, item3])// 這裡指的是垂直的間距了
group.interItemSpacing .fixed(5) //2 *5 10 group.edgeSpacing NSCollectionLayoutEdgeSpacing(leading: nil, top: nil, trailing: .fixed(10), bottom: nil)let section NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior .continuousGroupLeadingBoundary
let layout UICollectionViewCompositionalLayout(section: section)
return layout 我们使用group.edgeSpacing来设定group的边界距离。这个edgeSpacing是一个类型为NSCollectionLayoutEdgeSpacing的属性它的初始化方法长这样init(leading: NSCollectionLayoutSpacing?, top: NSCollectionLayoutSpacing?, trailing: NSCollectionLayoutSpacing?, bottom: NSCollectionLayoutSpacing?)利用这个对象我们可以描述一个方型的上下左右的边界距离。 6、自定义布局Custom Layout 图24 从这个看起来像小朋友下楼梯的layout开始我们可以看看如何做出完全定制的group layout也就是如何利用NSCollectionLayoutGroup的.custom这个builder method。 let height: CGFloat 120.0
let groupLayoutSize NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(height))
let group NSCollectionLayoutGroup.custom(layoutSize: groupLayoutSize) { (env) - [NSCollectionLayoutGroupCustomItem] inlet size env.container.contentSizelet spacing: CGFloat 8.0let itemWidth (size.width-spacing*4)/3.0return [NSCollectionLayoutGroupCustomItem(frame: CGRect(x: 0, y: 0, width: itemWidth, height: height/3.0)),NSCollectionLayoutGroupCustomItem(frame: CGRect(x: (itemWidthspacing), y: height/3.0, width: itemWidth, height: height/3.0)),NSCollectionLayoutGroupCustomItem(frame: CGRect(x: ((itemWidthspacing)*2), y: height*2/3.0, width: itemWidth, height: height/3.0))]
} 这个地方看起来有点复杂让我们先从.custom的定义开始看起 open class func custom(layoutSize: NSCollectionLayoutSize, itemProvider: escaping NSCollectionLayoutGroupCustomItemProvider) 这里的.custom是指明我们这个group里面的layout要我们自己来决定它的第一个参数layoutSize大家都应该非常熟悉了就是指定这个group的大小。那么什么是 itemProvider呢它实际上是一个(NSCollectionLayoutEnvironment) - [NSCollectionLayoutGroupCustomItem]的闭包在系统准备呈现item之前会调用这个闭包请求闭包提供每一个item的位置信息。这个闭包会传入一个代表容器的 NSCollectionLayoutEnvironment对象进来然后我们就可以利用这个environment提供的信息来计算item的大小和位置并且在闭包把算好的item大小和位置通过类型是NSCollectionLayoutGroupCustomItem的对象传出来返回值是一个array代表的是你需要指明这个group总共会有几个item。所以你会看到上面我们通过 env.container.contentSize获取容器的大小也就是group的大小。利用这个信息算出这个group里面的三个对象的绝对位置再用NSCollectionLayoutGroupCustomItem打包位置信息传回去而拿到这些信息的NSCollectionLayoutGroup就可以知道在这个group里面如何呈现这些item了。 04 辅助视图Supplementary view Demo4-Supplementary CollectionView管理着三个基本的视图类:cell,supplementary items , decoration items。 Supplementary view的常见用途三个方面Badge页眉和页脚。页眉和页脚支持固定或随内容滚动。 新类型NSCollectionLayoutAnchor。 1、创建Badge 1右上角外移 首先我们为badge定义一个NSCollectionLayoutAnchor。通过指定[.top,.trailing]。我们将badge定位在父元素的右上角而使用fractionalOffset 将badge的中心定位在角落。fractionalOffset为.zero会将整个标记定位到父标记中。https://developer.apple.com/documentation/uikit/nscollectionlayoutanchor如前所述我们需要为Item创建一个尺寸因此这里指定badge的宽度和高度为固定值我们初始化了NSCollectionLayoutSupplementaryItem给它指定了大小、锚点和一个elementKind根据它在collectionView:viewForSupplementaryElementOfKind:atIndexPath:中标识标识现在我们已经准备好了Badge我们只需要将它分配给我们的项目。要做到这一点只需替换NSCollectionLayoutItem初始化器并添加supplementaryItems和传递的badge。 这里我们看到我们马上创建了NSCollectionLayoutAnchor。我们指定edge。我们希望这个badge被固定在该特定单元格的顶部尾部。我们想让它跳出几何图形向外突出极小的高度它们并不在Cell本身的几何结构中。这个fractionalOffset偏移让我们有能力往外移。 在X轴正方向处移动30%在负Y处也移动30%然后我们用badgeSize和elementKind定义了CollectionLayout的SupplementaryItem。引用CollectionView的视图类用那个注册的Supplementary view类型 。然后指定容器的锚点指定它将如何关联。现在我们有了supplementary的定义我们需要把它和某个东西联系起来。它需要与一个元素一个单元格相关联。在这个例子中我们会用一个扩展的初始化方法来初始化它它接受一个supplementary的数组。 图25 图26 2左上角内顶点 如下图所示我们将badge移动到top leading位置并且fractionalOffset是item的偏移量指定0它将badge锚定在item内。 如果我们想告诉Compositional Layout我们想要supplementary views我们需要将它们关联到NSCollectionLayoutItem或NSCollectionLayoutGroup。这可以通过init或稍后的supplementaryItems属性来实现。这两种情况都需要一个NSCollectionLayoutSupplementaryItem类型的数组。这个类允许我们定义supplementary items。你不能在section上添加NSCollectionLayoutSupplementaryItem Item。对于sectionNSCollectionLayoutBoundarySupplementaryItem我们将在下一节中看到。 图27 3NSCollectionLayoutAnchor NSCollectionLayoutAnchor→定义如何将supplementary item附加到集合视图中的Item上的对象。可以使用锚点将supplementary item附加到特定Item。锚点包含有关supplementary item在item上的位置的信息包括: edge:边缘或一组边缘通过指定两条相邻的边可以将supplementary item附加到单个边或拐角上Item的偏移量。默认情况下supplementary item被锚定在它所附加的Item的指定边缘内。可以通过在创建锚点时提供自定义偏移量来更改此位置。 图28 Badge用户界面代码 class BadgeSupplementaryView: UICollectionReusableView {static let reuseIdentifer BadgeSupplementaryViewoverride init(frame: CGRect) {super.init(frame: frame)self.backgroundColor .greenself.clipsToBounds trueself.layer.cornerRadius 10}required init?(coder: NSCoder) {fatalError(init(coder:) has not been implemented)}
} 4页眉页脚 首先定义一些元素类型 struct ElementKind {static let badge badge-element-kindstatic let background background-element-kindstatic let sectionHeader section-header-element-kindstatic let sectionFooter section-footer-element-kindstatic let layoutHeader layout-header-element-kindstatic let layoutFooter layout-footer-element-kind
} 如下图所示我们做了一些事情 创建一个badge将其锚定到单元格Item上然后创建一个group将item分配给一个group最后我们创建sectionHeader NSCollectionLayoutBoundarySupplementaryItem特别注意跟badge区别给它布局headerFooterSize并给头部对齐作为顶部和底部。注意headerFooterSize是动态的这意味着它会随着内容的增加而增长这是因为我们提供的是估计高度而不是绝对高度然后我们提供boundarySupplementaryItems并传递页眉和页脚给section。 NSCollectionLayoutBoundarySupplementaryItem→与部分边界边相关的supplementary items的数组例如页眉和页脚。 NSCollectionLayoutSupplementaryItem→一个对象用于添加页眉或页脚的集合视图。边界supplementary item是supplementary item的特殊类型(NSCollectionLayoutSupplementaryItem)。可以使用边界supplementary item向集合视图的某个部分或整个集合视图添加页眉或页脚。每种类型的supplementary item项必须具有唯一的元素类型。考虑以一种可以直接识别每个元素的方式一起跟踪这些字符串。 图29 图30 Diffable数据源有一个supplementaryViewProvider属性我们可以用它来提供supplementary views。这可以是一个闭包也可以定义一个方法该方法接受collectionview、kind和indexPath并分配这个方法。 图31 注册supplementary view非常重要否则应用程序会崩溃 图32 5固定header和footer 如图33所示我们固定了header和footer pinToVisibleBounds→布尔值表示页眉或页脚是固定在它所附加的部分或布局的顶部或底部可见边界上。这个属性的默认值为false表示边界补充项(页眉或页脚)在滚动期间保持在原来的位置并在其部分或布局滚动时移动到屏幕外。将该属性的值设置为true以将边界补充项固定在它所附加的部分或布局的可见边界上。这样在显示边界补充项时它所附加的部分或布局的任何部分都是可见的。 图33 图34 6zindex zindex→补充项相对于该段内其他项的垂直堆叠顺序。这个属性用于在布局过程中确定元素从前到后的顺序。具有较高索引值的项目出现在具有较低索引值的项目的顶部。具有相同值的元素具有不确定的顺序。 图35 7多个Badge同时展示 现在我们在每个项目中添加了两个badge注意每个supplementary item都应该有唯一的elementKind还有一件事elementKind可以是任何字符串。 图36 DataSource 图37 注册 supplementary View 图38 图39 2、Decoration Items Demo5-Decoration 除了supplementary items之外我们还可以使用装饰项Decoration Items自定义section布局。这将允许我们轻松地为section添加背景。我们要创建的背景视图非常简单(一个带圆角半径的灰色矩形)所以用代码来完成。 好吧。到目前为止你已经使用了全新的iOS 13 card演示整个card设计语言贯穿整个系统。我们在滚动UI中也看到了这一点所有内容都与卡片逻辑地组合在一起。这很适合CollectionView因为我们一直支持装饰视图的概念。在过去你必须自己计算。好吧现在我们使用组合布局让它简单了很多。我们用CollectionLayoutDecorationItem来支持它。你只需要用element kind创建它就可以了。这是用来在section内容背后建立一个视图给你漂亮的视觉分组。要构建它只需要一行代码。然后将它添加到section中你只需要指定Item就可以了。 如下图所示我们为背景视图创建了布局它将为每个部分显示我们使用布局进行注册。 layout.register(BackgroundSupplementaryView.self, forDecorationViewOfKind: “background”)→注册一个类用于为集合视图创建装饰视图。这个方法让布局对象有机会注册一个装饰视图在集合视图中使用。装饰视图为部分或整个集合视图提供可视化的装饰但不与集合视图的数据源提供的数据绑定。你不需要显式地创建装饰视图。 注册一个后由layout对象决定何时需要一个装饰视图并从它的layoutAttributesForElements(in:)方法返回相应的布局属性。对于指定装饰视图的布局属性集合视图创建(或重用)一个视图并根据注册的信息自动显示它。 如果您以前使用相同的类型字符串注册了类或nib文件则您在viewClass参数中指定的类将替换旧条目。如果你想注销装饰视图可以为viewClass指定nil。 图40 背景卡视图将每个部分背景视图显示为卡片 图41 3、全局 Header , Footer 和 Decorative View 如下图所示我们在布局中全局添加了header和footer而不是section因为section和footer的pinToVisibleBounds是true所以它将在屏幕上可见。 UICollectionViewCompositionalLayoutConfiguration→定义滚动方向、区域间距和布局的页眉或页脚的对象。你可以使用布局配置来修改集合视图布局的默认滚动方向在布局的每个部分之间添加额外的间距并为整个布局添加页眉或页脚。你可以在创建UICollectionViewCompositionalLayout时传入这个配置或者你可以在现有的布局上设置configuration属性。如果在现有布局上修改配置系统会使布局失效以便用新的配置更新它。 图42 如下图所示现在注释pinToVisibleBounds当滚动全局header和footer时也会滚动 图43 05 结语 通过学习本文,我们全面了解了UICollectionViewCompositionalLayout的强大功能。它通过Item、Group和Section的分层结构,为构建复杂的自定义布局提供了巨大的灵活性。我们还学习了各种高级布局技巧,如嵌套布局组、装饰视图、全局头尾等。Compositional Layout使我们可以用声明式代码优雅地表达布局意图。它是在iOS上构建复杂滚动UI的未来方向。希望本文能让您对Compositional Layout有更深的理解,并在项目中大胆运用它。掌握它,我们就能创造出更加惊人的用户体验 参考 NSCollectionLayoutDimensionhttps://developer.apple.com/documentation/uikit/nscollectionlayoutdimension