我的目标是在UICollectionView
中具有动态大小的单元格。另外,当contentWidth
的可滚动UICollectionView
小于Collection的bounds
时,我希望项目在集合内居中。
到目前为止的方法:
我正在尝试以最干净的方式进行此操作。我有自动调整大小的UICollectionViewCells
,其中“自动布局”可以根据内容的约束确定单元格的大小。为此,我的单元将覆盖preferredLayoutAttributesFitting
函数,并为其内容返回适当的宽度。
我的自定义UICollectionViewFlowLayout
子类使用UICollectionViewCell
提供的宽度信息来适当地调整单元格的大小。
在我的自定义UICollectionViewFlowLayout
上唯一覆盖的方法是:
layoutAttributesForItem(at...)
layoutAttributesForElementsIn(rect...)
shouldInvalidateLayout(forBoundsChange...)
动态调整UICollectionViewCells
的大小是完美的。
问题:
使用这种方法,我对每个单元的大小没有先验知识(每个单元可以不同)。我想实现UICollectionView
的{{1}}方法来提供插图,以使我的内容在收藏夹视图中居中。为此,我认为我应该实现并覆盖
delegate
问题在于inset方法是在布局过程的早期调用的,我不知道在布局过程中将确定的collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets
。
这里是各种布局方法的堆栈,用于一次完整的布局遍历,按它们运行的顺序进行:
contentSize
对于一次布局传递,将多次调用各种布局方法,但是直到最后,才可以从不同单元格获得来自自动布局的尺寸信息,以传递到布局。
我注意到prepare started
prepare finished
layoutAttributesForElementsInRect started
layoutAttributesForElementsInRect finished
layoutAttributesForElementsInRect started
layoutAttributesForElementsInRect finished
layoutAttributesForElementsInRect started
layoutAttributesForElementsInRect finished
prepare started
insetForSectionAt 0 called <--- The datasource has knowledge of the presence of cells but the cells themselves are not yet dequeued
prepare finished
layoutAttributesForElementsInRect started
layoutAttributesForItemAt [0, 0] started
layoutAttributesForItemAt [0, 0] finished
layoutAttributesForItemAt [0, 1] started
layoutAttributesForItemAt [0, 1] finished
layoutAttributesForItemAt [0, 2] started
layoutAttributesForItemAt [0, 2] finished
layoutAttributesForElementsInRect finished
layoutAttributesForElementsInRect started
layoutAttributesForItemAt [0, 0] started
layoutAttributesForItemAt [0, 0] finished
layoutAttributesForItemAt [0, 1] started
layoutAttributesForItemAt [0, 1] finished
layoutAttributesForItemAt [0, 2] started
layoutAttributesForItemAt [0, 2] finished
layoutAttributesForElementsInRect finished
prepare started
prepare finished
layoutAttributesForElementsInRect started
<--- At this point Cells DO return valid sizes through Autolayout
layoutAttributesForItemAt [0, 0] started
layoutAttributesForItemAt [0, 0] finished
layoutAttributesForItemAt [0, 1] started
layoutAttributesForItemAt [0, 1] finished
layoutAttributesForItemAt [0, 2] started
layoutAttributesForItemAt [0, 2] finished
layoutAttributesForElementsInRect finished
委托函数在单元正确地出队并允许其自动布局约束运行之前就被调用。因此,不可能在调用插图时将正确的值告知插图。
使用这种方法,是否有可能在布局通过后强制调用重新计算sectionInset
还是我应该完全改变技术?
我尝试过的事情:
我尝试从sectionInsetAt
委托函数中查询collectionViewLayout
的{{1}}。不幸的是,由于布局正在进行中,因此无法在布局过程中使用。它会导致崩溃并显示错误:
collectionViewContentSize
我尝试使用insetForSectionAt
的{{1}}方法在执行[CollectionView] An attempt to update layout information was detected while already in the process of computing the layout (i.e. reentrant call). This will result in unexpected behaviour or a crash. This may happen if a layout pass is triggered while calling out to a delegate. UICollectionViewFlowLayout instance is (<App.ViewportToolbarFlowLayout: 0x7f8a5261add0>)
委托方法的过程中获得大小,但是在运行时它无法提供正确的数据在布局过程中。
答案 0 :(得分:0)
在此处发布答案。我发现Alex Koshy的this answer对我有很大帮助。
基本上,该工作必须在layoutAttributesForElementsIn(rect...)
中进行,因为它是唯一可获得完整版式图片的地方。我已经调整了Alex的代码以处理水平UICollectionViews
和垂直scrollDirection
(可能不是每个人都想要的,但正是我所需要的)。我使用布局的scrollDirection
属性来确定元素是水平居中(当.horizontal
是scrollDirection
时还是垂直居中(当.vertical
是override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let collectionView = collectionView else { return nil }
guard let superLayoutAttributes = super.layoutAttributesForElements(in: rect) else { return nil }
let computedAttributes = superLayoutAttributes.compactMap { layoutAttribute -> UICollectionViewLayoutAttributes? in
return layoutAttribute.representedElementCategory == .cell ? layoutAttributesForItem(at: layoutAttribute.indexPath) : layoutAttribute
}
guard let attributes = NSArray(array: computedAttributes, copyItems: true) as? [UICollectionViewLayoutAttributes] else { return nil }
let interItemSpacing = minimumInteritemSpacing
switch scrollDirection {
case .horizontal:
let leftPadding: CGFloat = self.sectionInset.left
var leftMargin: CGFloat = leftPadding // Modified to determine origin.x for each item
var maxY: CGFloat = -1.0 // Modified to determine origin.y for each item
var rowSizes: [[CGFloat]] = [] // Tracks the starting and ending x-values for the first and last item in the row
var currentRow: Int = 0 // Tracks the current row
attributes.forEach { layoutAttribute in
// Each layoutAttribute represents its own item
if layoutAttribute.frame.origin.y >= maxY {
// This layoutAttribute represents the left-most item in the row
leftMargin = leftPadding
currentRow += rowSizes.isEmpty ? 0 : 1
// Register its origin.x in rowSizes for use later
rowSizes.append([leftMargin, 0])
}
layoutAttribute.frame.origin.x = leftMargin
leftMargin += layoutAttribute.frame.width + interItemSpacing
maxY = max(layoutAttribute.frame.maxY, maxY)
// Add right-most x value for last item in the row
rowSizes[currentRow][1] = leftMargin - interItemSpacing
}
// At this point, all cells are left aligned
// Reset tracking values and add extra left padding to center align entire row
leftMargin = leftPadding
maxY = -1.0
currentRow = 0
attributes.forEach { (layoutAttribute) in
if layoutAttribute.frame.origin.y >= maxY {
// This layoutAttribute represents the left-most item in the row
leftMargin = leftPadding
// Need to bump it up by an appended margin
let rowWidth = rowSizes[currentRow][1] - rowSizes[currentRow][0] // last.x - first.x
let appendedMargin = (collectionView.frame.width - leftPadding - rowWidth - leftPadding) / 2
leftMargin += appendedMargin
currentRow += 1
}
layoutAttribute.frame.origin.x = leftMargin
leftMargin += layoutAttribute.frame.width + interItemSpacing
maxY = max(layoutAttribute.frame.maxY, maxY)
}
case .vertical:
let topPadding: CGFloat = self.sectionInset.top
var topMargin: CGFloat = topPadding
var maxX: CGFloat = -1.0 // Modified to determine origine.x for each item
var colSizes: [[CGFloat]] = [] // Tracks the starting and ending y-values for the first and last item in the column
var currentCol: Int = 0
attributes.forEach { (layoutAttribute) in
if layoutAttribute.frame.origin.x >= maxX {
topMargin = topPadding
currentCol += colSizes.isEmpty ? 0 : 1
colSizes.append([topMargin, 0])
}
layoutAttribute.frame.origin.y = topMargin
topMargin += layoutAttribute.frame.height + interItemSpacing
maxX = max(layoutAttribute.frame.maxX, maxX)
colSizes[currentCol][1] = topMargin - interItemSpacing
}
topMargin = topPadding
maxX = -1.0
currentCol = 0
attributes.forEach { (layoutAttribute) in
if layoutAttribute.frame.origin.x >= maxX {
topMargin = topPadding
let colWidth = colSizes[currentCol][1] - colSizes[currentCol][0]
let appendedMargin = (collectionView.frame.height - topPadding - colWidth - topPadding) / 2
topMargin += appendedMargin
currentCol += 1
}
layoutAttribute.frame.origin.y = topMargin
topMargin += layoutAttribute.frame.height + interItemSpacing
maxX = max(layoutAttribute.frame.maxX, maxX)
}
}
return attributes
}
时)
代码如下:
addToFavorites({ state }, payload) {
firebase
.database()
.ref('users/' + state.user.user.uid + '/favorites')
.once('value', snapshot => {
if (snapshot.exists()) {
firebase
.database()
.ref('users')
.child(state.user.user.uid + '/favorites')
.push(payload);
}
});