ios UICollectionView 在边界更改时使布局无效
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/29023473/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
UICollectionView Invalidate Layout On Bounds Changes
提问by Stussa
I currently have the following snippet for calculating UICollectionViewCells
sizes:
我目前有以下用于计算UICollectionViewCells
尺寸的片段:
- (CGSize)collectionView:(UICollectionView *)mainCollectionView
layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)atIndexPath
{
CGSize bounds = mainCollectionView.bounds.size;
bounds.height /= 4;
bounds.width /= 4;
return bounds;
}
This works. However, I'm now adding a keyboard observer in viewDidLoad
(which is triggering the the delegate and data source methods for the UICollectionView
before it appears and resizes itself from the storyboard). The bounds are thus wrong. I also would like to support rotation. What's a good way of handling these two edge cases and re-calculating the sizes if the UICollectionView
changes size?
这有效。但是,我现在正在添加一个键盘观察器viewDidLoad
(它UICollectionView
在它出现之前触发委托和数据源方法,并从故事板调整自身大小)。因此界限是错误的。我也想支持轮换。处理这两种边缘情况并在UICollectionView
更改大小时重新计算大小的好方法是什么?
回答by tubtub
The solution for invalidating your layout when the bounds of the collection view changes is to override shouldInvalidateLayoutForBoundsChange:
and return YES
.
It's also stated in the documentation: https://developer.apple.com/documentation/uikit/uicollectionviewlayout/1617781-shouldinvalidatelayoutforboundsc
当集合视图的边界发生变化时使布局无效的解决方案是覆盖shouldInvalidateLayoutForBoundsChange:
和返回YES
。它也在文档中说明:https: //developer.apple.com/documentation/uikit/uicollectionviewlayout/1617781-shouldinvalidatelayoutforboundsc
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
This should cover rotation support as well. If it doesn't, implement viewWillTransitionToSize:withTransitionCoordinator:
这也应该包括旋转支持。如果没有,请执行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)
{
}];
}
回答by eXtreme
You should handle the case when the collection view size is changed. If you change the orientation or constraints, viewWillLayoutSubviewsmethod will be triggered.
You should invalidate the current collection view layout. After the layout is invalidated by using invalidateLayoutmethod, the UICollectionViewDelegateFlowLayout methods will be triggered.
您应该处理更改集合视图大小的情况。如果更改方向或约束,将触发viewWillLayoutSubviews方法。
您应该使当前的集合视图布局无效。使用invalidateLayout方法使布局失效后,将触发 UICollectionViewDelegateFlowLayout 方法。
Here's the example code:
这是示例代码:
- (void)viewWillLayoutSubviews { [super viewWillLayoutSubviews]; [mainCollectionView.collectionViewLayout invalidateLayout]; }
回答by possen
This approach allows you do it without subclassing the layout, instead you add it to your, presumably already existing UICollectionViewController
subclass, and avoids the potential of recursively calling viewWillLayoutSubviews
it is a variation of the accepted solution, slightly simplified in that it does not use the transitionCoordinator
. In Swift:
这种方法允许您在不继承布局的情况下执行此操作,而是将其添加到您的可能已经存在的UICollectionViewController
子类中,并避免递归调用viewWillLayoutSubviews
它的可能性是已接受解决方案的变体,稍微简化,因为它不使用transitionCoordinator
. 在斯威夫特:
override func viewWillTransition(
to size: CGSize,
with coordinator: UIViewControllerTransitionCoordinator
) {
super.viewWillTransition(to: size, with: coordinator)
collectionViewLayout.invalidateLayout()
}
回答by Michael
Although @tubtub's answer is valid, some of you may experience the following error: The behaviour of the UICollectionViewFlowLayout is not defined
.
虽然@tubtub 的回答是有效的,但你们中的一些人可能会遇到以下错误:The behaviour of the UICollectionViewFlowLayout is not defined
.
First of all, remember to override the shouldInvalidateLayout
within your CustomLayout
class. (example below)
首先,记得shouldInvalidateLayout
在你的CustomLayout
类中覆盖。(下例)
You should then consider if all the elements within your view's have changed its sizes, according to a new layout (see he optional steps within the example code).
然后,您应该考虑视图中的所有元素是否都根据新布局更改了其大小(请参阅示例代码中的可选步骤)。
Here is the following code, to get you started. Depending on the way your UI is created, you may have to experiment to find the right view to call the recalculate
method, yet that should guide you towards the first steps.
这是以下代码,让您开始。根据创建 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
}
}