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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 05:11:01  来源:igfitidea点击:

UICollectionView Invalidate Layout On Bounds Changes

iosobjective-c

提问by Stussa

I currently have the following snippet for calculating UICollectionViewCellssizes:

我目前有以下用于计算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 UICollectionViewbefore 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 UICollectionViewchanges 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

  1. You should handle the case when the collection view size is changed. If you change the orientation or constraints, viewWillLayoutSubviewsmethod will be triggered.

  2. You should invalidate the current collection view layout. After the layout is invalidated by using invalidateLayoutmethod, the UICollectionViewDelegateFlowLayout methods will be triggered.

  1. 您应该处理更改集合视图大小的情况。如果更改方向或约束,将触发viewWillLayoutSubviews方法。

  2. 您应该使当前的集合视图布局无效。使用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 UICollectionViewControllersubclass, and avoids the potential of recursively calling viewWillLayoutSubviewsit 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 shouldInvalidateLayoutwithin your CustomLayoutclass. (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 recalculatemethod, 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
        }
    }