我目前有以下用于计算UICollectionViewCells
尺寸的代码段:
- (CGSize)collectionView:(UICollectionView *)mainCollectionView
layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)atIndexPath
{
CGSize bounds = mainCollectionView.bounds.size;
bounds.height /= 4;
bounds.width /= 4;
return bounds;
}
这很有效。但是,我现在在viewDidLoad
中添加一个键盘观察器(它在UICollectionView
出现之前触发了委托和数据源方法,并从故事板中调整自身大小)。因此,界限是错误的。我也想支持轮换。如果UICollectionView
改变大小,处理这两个边缘情况并重新计算尺寸的好方法是什么?
答案 0 :(得分:45)
当集合视图的边界发生更改时,使布局无效的解决方案是覆盖shouldInvalidateLayoutForBoundsChange:
并返回YES
。
它也在文档中说明:https://developer.apple.com/documentation/uikit/uicollectionviewlayout/1617781-shouldinvalidatelayoutforboundsc
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
这也应该包括旋转支持。如果没有,请实施viewWillTransitionToSize:withTransitionCoordinator:
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[super viewWillTransitionToSize:size
withTransitionCoordinator:coordinator];
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context)
{
[self.collectionView.collectionViewLayout invalidateLayout];
}
completion:^(id<UIViewControllerTransitionCoordinatorContext> context)
{
}];
}
答案 1 :(得分:10)
您应该在更改集合视图大小时处理这种情况。如果您更改方向或约束,将触发viewWillLayoutSubviews方法。
您应该使当前集合视图布局无效。使用invalidateLayout方法使布局无效后,将触发UICollectionViewDelegateFlowLayout方法。
以下是示例代码:
- (void)viewWillLayoutSubviews { [super viewWillLayoutSubviews]; [mainCollectionView.collectionViewLayout invalidateLayout]; }
答案 2 :(得分:1)
此方法允许您执行此操作而无需将布局子类化,而是将其添加到您可能已经存在的UICollectionViewController
子类中,并且避免了递归调用viewWillLayoutSubviews
的可能性,这是已接受的变体解决方案,略有简化,因为它不使用transitionCoordinator
。在Swift中:
override func viewWillTransition(
to size: CGSize,
with coordinator: UIViewControllerTransitionCoordinator
) {
super.viewWillTransition(to: size, with: coordinator)
collectionViewLayout.invalidateLayout()
}
答案 3 :(得分:0)
尽管@tubtub的答案是有效的,但有些人可能会遇到以下错误:The behaviour of the UICollectionViewFlowLayout is not defined
。
首先,请记住在shouldInvalidateLayout
类中覆盖CustomLayout
。 (下面的示例)
然后,您应该根据新布局考虑视图中所有元素的大小是否已更改(请参见示例代码中的可选步骤)。
以下是以下代码,可以帮助您入门。根据创建UI的方式,您可能必须尝试找到合适的视图来调用recalculate
方法,但这应该可以指导您迈出第一步。
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
/// (Optional) Additional step 1. Depending on your layout, you may have to manually indicate that the content size of a visible cells has changed
/// Use that step if you experience the `the behavior of the UICollectionViewFlowLayout is not defined` errors.
collectionView.visibleCells.forEach { cell in
guard let cell = cell as? CustomCell else {
print("`viewWillTransition` failed. Wrong cell type")
return
}
cell.recalculateFrame(newSize: size)
}
/// (Optional) Additional step 2. Recalculate layout if you've explicitly set the estimatedCellSize and you'll notice that layout changes aren't automatically visible after the #3
(collectionView.collectionViewLayout as? CustomLayout)?.recalculateLayout(size: size)
/// Step 3 (or 1 if none of the above is applicable)
coordinator.animate(alongsideTransition: { context in
self.collectionView.collectionViewLayout.invalidateLayout()
}) { _ in
// code to execute when the transition's finished.
}
}
/// Example implementations of the `recalculateFrame` and `recalculateLayout` methods:
/// Within the `CustomCell` class:
func recalculateFrame(newSize: CGSize) {
self.frame = CGRect(x: self.bounds.origin.x,
y: self.bounds.origin.y,
width: newSize.width - 14.0,
height: self.frame.size.height)
}
/// Within the `CustomLayout` class:
func recalculateLayout(size: CGSize? = nil) {
estimatedItemSize = CGSize(width: size.width - 14.0, height: 100)
}
/// IMPORTANT: Within the `CustomLayout` class.
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
guard let collectionView = collectionView else {
return super.shouldInvalidateLayout(forBoundsChange: newBounds)
}
if collectionView.bounds.width != newBounds.width || collectionView.bounds.height != newBounds.height {
return true
} else {
return false
}
}
答案 4 :(得分:0)
UICollectionViewFlowLayout
shouldInvalidateLayout(forBoundsChange:)
以返回true
。您可以使用此方法来确定当特定范围发生更改时,此布局是否应自动调用invalidateLayout(with:)
。基类UICollectionViewLayout
总是返回false,也就是说,在边界改变时不会使任何内容失效。但是,UICollectionViewFlowLayout
有时会返回true来完成自己的工作。 要违反现有功能,您一开始会叫super,只有在super返回false时,您才做额外的工作。 invalidateLayout(with:)
,以控制要完全无效的内容。 context
参数管理有关此布局的所有详细信息。对于FlowLayout,它提供了其他控件,包括invalidateFlowLayoutDelegateMetrics
(是否要调整项目的大小,即通过调用collectionView(:layout:sizeForItemAt:)
重新计算项目的大小)和invalidateFlowLayoutAttributes
(是否要重新分发项目,例如,重新计算项目的位置)。您可以在调用super之前操纵此context
。final class FlowLayout: UICollectionViewFlowLayout {
/// A cache for `newBounds` parameter in `shouldInvalidateLayout(forBoundsChange:)`.
var bounds: CGRect?
/// Should we invalidate both size and layout information for next layout.
var invalidatesAll = false
override func invalidateLayout(with context: UICollectionViewLayoutInvalidationContext) {
guard let flowContext = context as? UICollectionViewFlowLayoutInvalidationContext else {
return
}
if invalidatesAll {
// Tell layout to recompute the size of items
flowContext.invalidateFlowLayoutDelegateMetrics = true
// Tell layout to recompute the layout of items
flowContext.invalidateFlowLayoutAttributes = true
// Reset this flag, because we do this only if there is a width change.
invalidatesAll = false
}
super.invalidateLayout(with: flowContext)
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
if super.shouldInvalidateLayout(forBoundsChange: newBounds) {
// If super (flowlayout) has already determined an invalidation, we simply hand over.
return true
}
// We make sure there is a width change to avoid unnecessory invalidations.
// Note: for the first time, `bounds` is always nil and flow layout always do first layout automatically, so we want to avoid this too.
let isWidthChanged = bounds != nil && newBounds.size.width != bounds?.size.width
if isWidthChanged {
// A width change happens!, and we want to recompute both size and layout on width change.
invalidatesAll = true
}
bounds = newBounds
return isWidthChanged
}
}