UICollectionViewFlowLayout 在方向更改时不会失效

Posted

技术标签:

【中文标题】UICollectionViewFlowLayout 在方向更改时不会失效【英文标题】:UICollectionViewFlowLayout not invalidating right on orientation change 【发布时间】:2013-05-02 15:20:37 【问题描述】:

我有一个带有 UICollectionViewFlowLayout 的 UICollectionView。我还实现了 UICollectionViewDelegateFlowLayout 协议。

在我的数据源中,我有一堆 UIViewControllers 响应自定义协议,所以我可以询问它们的大小和其他一些东西。

在 FlowLayout 委托中,当它要求 sizeForItemAtIndexPath: 时,我返回从我的协议中获得的项目大小。实现我的协议的 ViewControllers 根据方向返回不同的项目大小。

现在,如果我将设备方向从纵向更改为横向,则没有问题(横向项目较大),但如果我将其更改回来,我会收到以下警告:

    the item width must be less that the width of the UICollectionView minus the section insets left and right values.
    Please check the values return by the delegate.

它仍然有效,但我不喜欢它收到警告,所以也许你可以告诉我我做错了什么。还有另一个问题。如果我不告诉我的 collectionViews collectionViewLayout 在 willAnimateRotationToInterfaceOrientation 中失效: sizeForItemAtIndexPath: 永远不会被调用。

希望你明白我的意思。如果您需要更多信息,请告诉我:)

【问题讨论】:

我也遇到了同样的问题,请问您发现问题所在了吗? 还没有,但我仍在研究解决方案。如果我找到了,我会告诉你的 【参考方案1】:

这个答案迟了,但接受的答案对我不起作用。我同情 OP 想要一个即发即弃的 UICollectionViewFlowLayout。我建议使视图控制器中的布局无效实际上是最好的解决方案。

我想要一个水平滚动的单元格行,在视图中心,纵向和横向。

我继承了 UICollectionViewFlowLayout。

我重写了 prepareLayout 以重新计算 insets,然后调用 [super prepareLayout]。

我覆盖了collectionViewContentSize 的getter 以使确定 内容大小正确。

即使边界随着重新定向而改变,布局也不会自行失效。

- (void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration

    // does the superclass do anything at this point?
    [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];

    // do whatever else you need before rotating toInterfaceOrientation

    // tell the layout to recalculate
    [self.collectionViewLayout invalidateLayout];

UICollectionViewFlowLayout 保持方向之间的滚动位置。 如果是正方形,则相同的单元格将居中。

【讨论】:

@cynistersix 有更现代的答案。旋转界面已被弃用。我确实认为使布局无效比重新加载数据更好。【参考方案2】:

我在这里使用了两个答案的组合来做我们所有人真正想做的事情是使集合视图看起来像一个表格视图,但不使用 UITableView 并在单个列中获取项目(很可能)

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator 
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];

    [self.collectionView.collectionViewLayout invalidateLayout];

【讨论】:

我建议你使布局无效而不是重新加载数据。 你必须有超能力@BobWakefield...我已经做出了这个改变:) 更新答案【参考方案3】:

我不知道它是否对某人有帮助,但最后一个答案都没有帮助我,因为我将一些 UIViews 用于 UIScrollView。 我根据某些事件以编程方式打开 UIViews。所以我不能使用- (void) willRotateToInterfaceOrientation:invalidateLayoutreloadData 因为: 1. invalidateLayout“在下一个视图布局更新周期中发生。” 2. reloadData “为了提高效率,集合视图只显示那些可见的单元格和补充视图。” 对我有用的是使用 collectionView 的父级(UIView)的宽度:

-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath 
int currentWidth = self.frame.size.width;

UIEdgeInsets sectionInset = [(UICollectionViewFlowLayout *)collectionView.collectionViewLayout sectionInset];
int fixedWidth = currentWidth - (sectionInset.left + sectionInset.right);

由于某种原因,collectionView 框架在调用此函数时具有纵向宽度...使用int currentWidth = self.frame.width 帮助我修复它。

【讨论】:

【参考方案4】:

@meelawsh 答案的变体,但在 Swift 中,并且没有 self.sizeForCollectionView 属性:

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) 

    super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)

    guard let collectionView = self.collectionView else 
        return
    

    if size.width > collectionView.frame.width 
        // bigger

        let animation: (UIViewControllerTransitionCoordinatorContext)->(Void) = 
            context in
            collectionView.collectionViewLayout.invalidateLayout()
        

        coordinator.animateAlongsideTransition(animation, completion: nil)
     else 
        // smaller
        collectionView.collectionViewLayout.invalidateLayout()
    

【讨论】:

我并没有真正感受到做coordinator.animateAlongsideTransition(animation, completion: nil) 的不同之处。所以我认为只有collectionView.collectionViewLayout.invalidateLayout() 就足够了。【参考方案5】:

ios8:我可以消除警告的唯一方法是在旋转到较大尺寸时在过渡期间调用 invalidate,在旋转到较小尺寸时在过渡之前调用。

    self.sizeForCollectionView = size; //used to determine collectionview size in sizeForItem
    if (size.width <= size.height) 
        [self.collectionView.collectionViewLayout invalidateLayout];
    

    [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) 
        if (size.width > size.height) 
            [self.collectionView.collectionViewLayout invalidateLayout];
        
     completion:^(id<UIViewControllerTransitionCoordinatorContext> context) 
    ];

【讨论】:

不让协调器沿过渡动画,简单地调用 invalidateLayout 对我不起作用。好像是这个【参考方案6】:

@0mahc0 的回答对我有用,但只是部分。

我没有旋转问题,因为我强制我的应用仅处于纵向模式,但我的解决方案可以帮助其他人解决同样的问题。

每次视图出现时都会显示警告。警告的原因很清楚,我们在 UICollectionView 的左右定义了 section insets。检查界面生成器,您将在指标中看到定义的那些值。

@0mahc0 的解决方案有效,但我想将单元格拉伸到最大宽度,所以我删除了部分的插图,否则我将有一个左右“边距”。

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
    return UIEdgeInsetsZero;

您可以转到界面生成器并将左右插入的值更改为零,而不是实现委托方法。

【讨论】:

在界面生成器中插入值为零,但我仍然收到此错误。【参考方案7】:

一种解决方案是使用 KVO 并监听集合视图的父视图框架的变化。调用 -reloadData 将起作用并消除警告。这里有一个潜在的时间问题......

// -loadView
[self.view addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew context:nil];

// -dealloc
[self.view removeObserver:self forKeyPath:@"frame"];

// KVO Method
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

    [collectionView_ reloadData];

【讨论】:

这可能有效,但根据文档,UIView 框架不符合 KVO,Bob Wakefield 的回答独立于任何黑客攻击。

以上是关于UICollectionViewFlowLayout 在方向更改时不会失效的主要内容,如果未能解决你的问题,请参考以下文章