setCollectionViewLayout:动画导致调试错误:快照未呈现的视图会导致空快照

时间:2014-03-17 10:22:20

标签: objective-c ios7 uicollectionviewlayout

我在iOS7中忙于UICollectionView。

我正在两个不同的布局之间更改我的集合视图的布局。这是UICollectionViewFlowLayout的子类。

这是我点按按钮时更改视图的方式:

-(void)changeViewLayoutButtonPressed
{
    self.changeLayout = !self.changeLayout;

    if (self.changeLayout){
        [self.tableViewLayout invalidateLayout];
        [self.tradeFeedCollectionView setCollectionViewLayout:self.grideLayout animated:YES];

    }

    else {

        [self.grideLayout invalidateLayout];
        [self.tradeFeedCollectionView setCollectionViewLayout:self.tableViewLayout animated:YES];

    }
}

此方法按预期工作,并使用漂亮的动画更改视图。但是在控制台中我收到这些消息:

  

快照未渲染的视图会导致空白   快照。确保您的视图至少在之前呈现过一次   屏幕更新后的快照或快照。

有关导致此问题的任何想法?

9 个答案:

答案 0 :(得分:7)

请扩展上面的评论...

我收到警告的奇怪之处在于我的cv有三个可能的数据源。所有这些数据基本上与以下数据相同:Collection 1,Collection 2,Collection 1& 2。

当我刚看到Collection 1或Collection 2时,我没有收到任何警告。只有当我看到这两个组合时,我才收到警告。关于这一点的奇怪之处在于,没有什么可以展示一切。集合1和集合2是从"整体"中提取的。那么一个额外的步骤。不确定这意味着什么。

无论如何,我不再收到消息。

这是我原来的:

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    [self arrangeCollectionView];
}

- (void)arrangeCollectionView
{
    UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)self.cv.collectionViewLayout;
    if (UIInterfaceOrientationIsPortrait(self.interfaceOrientation))
    {
        flowLayout.sectionInset =  UIEdgeInsetsMake(16, 16, 16, 16);
        flowLayout.minimumInteritemSpacing = 16;
        flowLayout.minimumLineSpacing = 16;
    }
    else
    {
        flowLayout.sectionInset =  UIEdgeInsetsMake(26, 26, 26, 26);
        flowLayout.minimumInteritemSpacing = 26;
        flowLayout.minimumLineSpacing = 26;
    }
    self.cv.collectionViewLayout = flowLayout;    
}

这给了我关于旋转到横向的信息,但是当旋转到肖像时没有。

这两项更改都没有消息:

<强> 1 即可。 使用

didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation

拨打arrangeCollectionView而不是

willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration

解决了警告问题,但过渡时间有点粗糙。

<强> 2 即可。 摆脱对UICollectionViewFlowLayout的明确更改并使用DelegateFlowLayout方法并从reloadData调用willAnimateRotation...

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    [self.cv reloadData];
}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    CGSize retval = CGSizeMake(172, 256);
    return retval;
}

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
    return UIInterfaceOrientationIsPortrait(self.interfaceOrientation) ? UIEdgeInsetsMake(16 + 66, 16, 16, 16) : UIEdgeInsetsMake(26 + 66, 26, 26, 26);
}

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
{
    return UIInterfaceOrientationIsPortrait(self.interfaceOrientation) ? 16 : 28;
}

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
{
    return UIInterfaceOrientationIsPortrait(self.interfaceOrientation) ? 16 : 28;
}

这对我来说效果最好。没有更多的消息和从一个布局到另一个布局的相当平滑的过渡。

希望这可以帮助您追踪它。我想在流程布局失效期间会拍摄并呈现给用户,并且正在拍摄此快照,同时布局已经在willRotate...中重置...也许无论如何。仍然不确定为什么我不断为2个数据子集提供零消息,并始终始终为完整集合提供消息。

祝你好运,希望有所帮助。

答案 1 :(得分:5)

这个问题也发生在我身上。 我在UIScreen.mainScreen().snapshotViewAfterScreenUpdates(true)方法之前调用setCollectionViewLayout来解决此问题。 我也不知道为什么。 我希望它可以提供帮助。

答案 2 :(得分:5)

我遇到了这个问题,因为我只在横向模式下使用UICollectionView,而且第一次旋转到横向(并且只是第一次)时,我会获得许多关于快照的日志条目。我意识到日志条目的数量与单元格数量和标题视图相匹配,因此我将此代码添加到自定义单元格类和标题视图类中,并且日志条目消失了:

- (UIView*)snapshotViewAfterScreenUpdates:(BOOL)afterUpdates
{
    UIGraphicsBeginImageContext(self.bounds.size);

    [self drawRect:self.bounds];

    UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    UIImageView* aView = [[UIImageView alloc] initWithFrame:self.bounds];

    [aView setImage:viewImage];

    return aView;
}

我需要做更多测试,看看这是否是正确的方法,但现在它正在发挥作用。

答案 3 :(得分:2)

当尚未呈现它的视图上的方法- (UIView *)snapshotViewAfterScreenUpdates:生成此控制台警告时。

要么在某个地方调用此方法,要么更有可能setCollectionViewLayout: animated:正在使用此方法,并且在使用它时集合视图未呈现。

我的建议是不要在集合视图上调用- (void)invalidateLayout,看看会发生什么。

答案 4 :(得分:2)

我遇到了同样的问题。我在我的集​​合视图中使用UICollectionViewFlowLayout。我想根据界面方向排列布局

像往常一样,我超越了willAnimateRotationToInterfaceOrientation:duration

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
    [super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
    UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout;
    layout.itemSize = aNewSize;
    [layout invalidateLayout];
}

控制台中出现相同的消息。也许动画制作的快照失败了。我想,子视图没有足够的时间被捕获。

之后,我尝试了你的解决方案。我使用委托方法而不是直接分配项目大小。然而,模拟器仍然告诉我有警告。这里有些奇怪的东西。在调试过程中,我发现如果单元格的总数只有4,控制台就不会显示警告消息。当我将数字更改为10时,消息再次出现。不知道为什么。

让我们继续。我认为这个问题可能是因为在动画期间做布局工作。所以我将代码移到willRotateToInterfaceOrientation:duration:

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
    [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
    UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout;
    layout.itemSize = aNewSize;
    [self.collectionViewLayout invalidateLayout];
} 

在第一个方法之前调用此方法。还有什么,消息不再显示

顺便说一下,如果你动态安排单元格,你应该注意视图边界何时发生变化。在willRotateToInterfaceOrientation:duration:中,interfaceOrientation属性和视图边界仍然是旧值。但当事情发生willAnimateRotationToInterfaceOrientation:duration:时,值已更新。

答案 5 :(得分:1)

虽然我尝试通过相同的调试错误自行修复此问题,并且在动画中看到一些闪光,但我尝试了以下操作,并且对我来说效果很好:

private var isListLayout = true{
    didSet{
        UIScreen.main.snapshotView(afterScreenUpdates: true)
        collectionView!.collectionViewLayout.invalidateLayout()
        if isListLayout {
            DispatchQueue.main.async {
               self.collectionView!.setCollectionViewLayout(ListLayout(), animated: true)
            }
        }else{
            DispatchQueue.main.async {
                self.collectionView!.setCollectionViewLayout(GridLayout(), animated: true)
            }
        }
    }
}

如果您忽略var,关键是调用方法 UIScreen.main.snapshotView(afterScreenUpdates: true)并在下一个主框架上更新新的布局: DispatchQueue.main.async { self.collectionView!.setCollectionViewLayout(ListLayout(), animated: true) } 这对我来说很好,没有更多的调试问题,也没有间歇性的浮华动画

答案 6 :(得分:0)

在使布局无效之后,立即将DispatchQueue.main.async中的collectionView layoutIfNeeded()函数放入。

    let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout

    flowLayout?.invalidateLayout()

    DispatchQueue.main.async { [weak self] in
          self?.collectionView.layoutIfNeeded()
    }

答案 7 :(得分:0)

我有一个带有可调整大小单元格的收集视图,每当我旋转设备方向时,该单元格都会发出此警告。如果在旋转后可见某个单元,但在旋转前看不到该单元,则该设备仍将尝试对尚未渲染的单元的旋转前版本进行快照。因此,我最后在集合视图控制器中进行了检查,以查看旋转之前可见的单元格。这是用于垂直滚动集合视图的,但是您可能只是将引用交换为x,y,minX,minY等,以使其适用于水平集合视图。

override func viewWillTransition(
    to size: CGSize,
    with coordinator: UIViewControllerTransitionCoordinator)
{
    super.viewWillTransition(to: size, with: coordinator)
    let contentOffset = collectionView.contentOffset

    let visibleFrame = view.safeAreaLayoutGuide.layoutFrame

    collectionView.visibleCells.forEach { cell in

        guard let customCell = cell as? CustomCell else {
            return
        }

        let minY = customCell.frame.minY - contentOffset.y
        let maxY = customCell.frame.maxY - contentOffset.y

        if
            visibleFrame.contains(CGPoint(x: visibleFrame.minX, y: minY)) ||
            visibleFrame.contains(CGPoint(x: visibleFrame.minX, y: maxY))
        {
            customCell.shouldSnapshot = true
        }
    }

}

其中自定义单元格是具有以下内容的UICollectionViewCell子类:

class CustomCell: UICollectionViewCell {

    var shouldSnapshot = false

    override func snapshotView(afterScreenUpdates afterUpdates: Bool) -> UIView? {
        if shouldSnapshot {
            shouldSnapshot = false
            return super.snapshotView(afterScreenUpdates: afterUpdates)
        } else {
            return nil
        }
    }
}

我认为这已经完全摆脱了。我不能仅仅使用collectionView.visibleCells返回的所有可见单元格来确定要快照的单元格,因为我仍在显示屏幕上几乎没有的单元格的日志消息(我目前的假设是,无论出于何种原因,安全区域不视为已渲染)。

答案 8 :(得分:0)

只需添加到UICollectionView

[pCollectionView snapshotViewAfterScreenUpdates:YES];

它将在Objective-C上查看,

pCollectionFlowLayot = [[UICollectionViewFlowLayout alloc] init];
[pCollectionFlowLayot setMinimumLineSpacing:10];
[pCollectionFlowLayot setMinimumLineSpacing:10];
[pCollectionFlowLayot setSectionInset:UIEdgeInsetsMake(10, 10, 10, 10)];

pCollectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:pCollectionFlowLayot];
[pCollectionView snapshotViewAfterScreenUpdates:YES];