如何使用构图布局为不同部分设置不同的背景?

Tyl*_*mes 6 uicollectionview swift uicollectionviewcompositionallayout

我想使用组合布局在集合视图中的不同部分有不同的背景。

目前我正在用来NSCollectionLayoutDecorationItem.background设置一个部分的背景。然而,这种类型的视图不会像补充视图或单元那样出列或回收。初始化后我无法对其进行更改,而且我自己也无法对其进行初始化。

我想使用颜色或背景图像,它是我从后端检索的用户数据的一部分,因此在编译时我不知道颜色或图像。

有没有办法动态设置某个部分的背景图像或颜色?

tfe*_*tfe 5

经过一番努力,我找到了一种方法来做到这一点。就其本身而言,它并不算太 hacky,但需要额外的 hack 来解决看似 UIKit 的错误。

注意事项

  1. 仅当节内容和节标题的高度都是绝对的并且提前已知时(至少在节布局提供程序中),这才有效。

    这在我的情况下有效,因为我的标题和单元格都是固定高度的,并且每个部分仅使用一排水平滚动单元格。

  2. 背景视图不得水平滚动(假设是垂直滚动的集合视图。

    就我而言,我希望固定部分背景图像,单元格在其上方水平(正交)滚动。

约束/假设

  1. 我们只能使用组合布局(在我的例子中是为了轻松实现正交滚动)。

  2. 用于设置背景视图的工具(问题中提到的)将不起作用,因为它无法通过补充视图注册或任何其他方式按部分进行配置。所以我们一定要使用常规的补充品。

  3. 补充项必须位于该部分上才能获得正确的大小,这意味着仅使用 NSCollectionLayoutBoundarySupplementaryItem。

    3.1. 虽然可以欺骗不同的类型(例如组上的非边界补充项)使其达到正确的大小和位置,但使用技巧会导致 UIKit 不知道背景何时实际可见,从而导致闪烁输入/输出滚动期间。

  4. 尽管附加到节,节补充项将仅相对于单元格垂直定位,忽略节的标题部分。

  5. 更糟糕的是,这种对齐方式与设置高度等于总部分高度相结合,会导致补充项拉伸单元格下方部分的高度,从而使部分太高。

  6. 有一个为补充项目设置位置偏移的工具,但是由于我只能假设是一个错误(即使偏移量为 0), 使用它会导致节标题高度折叠到零,从而导致单元格下面向上移动并重叠它

解决方案

因此,为了解决所有这些问题,我所做的是:

  1. 将背景补充项目大小设置为标题+单元格/行+它们之间的任何间距的已知高度。
  2. 通过垂直偏移标题高度,将背景项拉到标题下方(使用偏移会导致单元格向上移动;请参阅约束 6)。
  3. 通过将顶部插入部分填充标题的高度 ( hackInsets),将单元格向下推。
  4. 将背景的前缘/后缘拉出到视图/屏幕边缘,以使其全出血(通常它会像内容一样插入)。我们通过在背景补充视图注册中的背景图像视图上设置负边距来实现这一点。
  5. 在背景视图上设置负 z 索引以将其保留在单元格和标题下方。

在代码中,相关部分如下所示:

let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(Self.headerHeight))
let headerSupplementaryItem = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: ElementKind.header, alignment: .top)

let hackInsets = NSDirectionalEdgeInsets(top: Self.headerHeight, leading: 0, bottom: 0, trailing: 0)
let backgroundHeight = MySectionHeaderView.nominalSize.height + Self.interItemSpacing + MyCollectionViewCell.nominalSize.height
let backgroundOffset = CGPoint(x: 0, y: -MySectionHeaderView.nominalSize.height)
let backgroundAnchor = NSCollectionLayoutAnchor(edges: .top, absoluteOffset: backgroundOffset)
let backgroundSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(backgroundHeight))
let backgroundSupplementaryItem = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: backgroundSize, elementKind: ElementKind.background, containerAnchor: backgroundAnchor)
backgroundSupplementaryItem.zIndex = -1

let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .continuous
section.contentInsets = Self.sectionInsets.summedWith(hackInsets)
section.boundarySupplementaryItems = [headerSupplementaryItem, backgroundSupplementaryItem]
Run Code Online (Sandbox Code Playgroud)

iOS 17 更新

上述技术的一方面似乎已在 iOS 17 或更高版本上停止工作。UIKit 似乎改变了向视图层次结构添加补充视图和单元格的顺序,导致背景图像位于(并遮挡)单元格/章节之上。

您可能认为设置 zIndex(就像我们在上面的步骤 5 中所做的那样)可以弥补这一点,但是在 iOS 17 下,似乎没有任何值导致单元格出现在背景补充视图的顶部。

我发现这个属性在 iOS 17 之前不起作用,但现在似乎也发生了这种情况。我们可以通过在背景视图的 CALayer 上设置 zPosition 来解决这个问题,如该答案中所述。

这修复了视觉外观(单元格现在再次出现在背景顶部),但背景视图在点击/交互方面仍然位于前面,因此我们还需要禁用补充视图上的用户交互。

这两个步骤似乎再次使其成为可行的解决方案。