setCollectionViewLayout:animated causing debug error: Snapshotting a view that has not been rendered results in an empty snapshot
Asked Answered
S

10

16

I am busy with UICollectionView in iOS7.

I am changing my collection view's layout between two different layouts. Which are a subclass of UICollectionViewFlowLayout.

This is how I change views when a button is tapped:

-(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];

    }
}

This method works as expected and the view is changed with a nice animation. However in the console I am receiving these messages:

Snapshotting a view that has not been rendered results in an empty snapshot. Ensure your view has been rendered at least once before snapshotting or snapshot after screen updates.

Any ideas as to what is causing this?

Sutlej answered 17/3, 2014 at 10:22 Comment(5)
Did you get anywhere with this? I am having the same problem with my UICollectionView when I modify its UICollectionViewFlowLayout on rotation. Only in the simulator though, not on the device so maybe its just a bit buggy.Dotation
Unfortunately I haven't. I'm getting it on device and simulator. Can you give me a heads up if you manage to solve it?Sutlej
My issue was just to do with changing the insets and spacing on my layout when the device was rotated. I got rid of it by returning edgeInsets and line/interitem spacing conditional on the device orientation in the UICollectionViewDelegateFlowLayout methods and then simply calling reloadData on the UICollectionView in willAnimateRotationToInterfaceOrientation:. Not sure if this helps you or not. If you want me to flesh this out a bit more let me know.Dotation
You could probably do the same thing using changeViewLayoutButtonPressed to set a bool - and finish up with a call to [cv reloadData]. Then add some ternary operators or an if/else in the Delegate methods to check the BOOL and return the appropriate values . It might work... ymmv of course.Dotation
Thanks for the help much appreciated. I am actually not sure exactly what is causing the issue. I'd love to get to the bottom of it as I have this error coming up more than I'd like it to. I believe its to do with constraints as well? The error does come up when changing layouts so I think i should start there? Maybe I should start a bounty. I'll update the question as well as my code has changed a little since then but still gives me an error. I'll try your suggestion today as well. Thanks dude!Sutlej
D
7

Just t expand on the comments above...

The strange thing about the warning I was getting was that my cv had three possible dataSources. All of them were basically the same data as: Collection 1, Collection 2, Collection 1 & 2.

When I was just viewing Collection 1 or Collection 2 I received no warnings. It was only when I was viewing the two combined I got warnings. The strange thing about this is there is less going on to show everything. Collection 1 and Collection 2 are extracted from the "Whole" so one extra step there. Not sure what this means.

Anyway I am no longer getting the messages.

Here's what I had originally:

- (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;    
}

And this was giving me the message on rotating to Landscape but not when rotating to Portrait.

Both of these changes resulted in no more messages:

1. Using

didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation

to call arrangeCollectionView instead of

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

Solved the warning problem but made the transition a bit cruder.

2. Getting rid of the explicit changes to the UICollectionViewFlowLayout and using the DelegateFlowLayout methods and calling reloadData from 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;
}

This worked best for me. No more messages and a fairly smooth transition from one layout to the other.

Hope this helps you to track it down. I guess a screenshot is taken and presented to the user during the flow layout invalidation and this snapshot is being taken while at the same time the layout is already being reset in willRotate...... maybe anyway. Still not sure why I got constantly zero message for the 2 subsets of data and consistently always messages for the complete set.

Good luck, hope it helps.

Dotation answered 23/4, 2014 at 7:15 Comment(2)
Thanks. I will experiment with it this weekend and see if I can solve it.Sutlej
Replacing invalidateLayout() with reloadData() helps!Ideograph
H
6

This problem happens to me too. I solve it by calling:UIScreen.mainScreen().snapshotViewAfterScreenUpdates(true) before setCollectionViewLayout method. I also do not know why. I wish it can be help.

Halima answered 9/7, 2015 at 5:37 Comment(1)
Thanks. I haven't looked at this in a while - will revisit it when refactoring time comes about.Sutlej
M
5

I had this problem as I was using a UICollectionView only when in landscape mode, and the first time when I'd rotate into landscape (and only the first time) I'd get many log entries about the snapshot. I realized that the number of log entries matched the number of cells + header views, so I added this code to the custom cell class and the header view class, and the log entries went away:

- (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;
}

I need to do some more testing and see if this is the correct approach, but for now it's working.

Microanalysis answered 21/9, 2015 at 17:24 Comment(0)
S
2

When the method - (UIView *)snapshotViewAfterScreenUpdates: on a view which has not yet rendered it generates this console warning.

Either you are calling this method somewhere, or more likely, the setCollectionViewLayout: animated: is using this method, and the collection view has not rendered when it is being used.

My recommendation would be to not call - (void)invalidateLayout on the collection view and see what happens.

Selfregard answered 17/3, 2014 at 13:22 Comment(1)
Thanks for the suggestion. I never call snapshotViewAfterScreenUpdates. I removed the invalidateLayout calls and it made zero difference.Sutlej
T
2

I came across the same problem. I was using UICollectionViewFlowLayout in my collection view. I wanted to arrange the layout according to interface orientation

As usual, I overrode willAnimateRotationToInterfaceOrientation:duration.

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

The same message appeared in the console. Maybe the animation made snapshotting fail. The sub views didn't have enough time to be captured, I guess.

Afterwards, I tried your solution. I used delegate methods instead of assigning item size directly. However, the simulator still told me there were warnings. Here's something weird. During debugging, I found the console wouldn't show warning messages if the total number of cells is only 4. When I changed the number to 10, the messages showed up again. Don't know why.

Let's continue. I thought the problem might result from doing layout stuff during animations. So I moved the code to 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];
} 

This method was called before the first one. What's more, the message didn't show up any more.

By the way, If you're arranging cells dynamically, you should beware of when the view bounds changes. In willRotateToInterfaceOrientation:duration: the interfaceOrientation property and view bounds are still old values. But when things come to willAnimateRotationToInterfaceOrientation:duration:, the values were updated.

Theona answered 9/11, 2014 at 12:43 Comment(0)
D
2

While trying to fix this issue on my own for the same debug error as well seeing some flash in the animation, i tried the following and it worked well for me:

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)
            }
        }
    }
}

If u ignore the var, key thing is calling the method: UIScreen.main.snapshotView(afterScreenUpdates: true) as well as updating the new layout on the next main frame : DispatchQueue.main.async { self.collectionView!.setCollectionViewLayout(ListLayout(), animated: true) } This works out nicely for me with no more debug issues and no more flashy animations intermittently

Disyllable answered 14/9, 2018 at 14:1 Comment(0)
T
1

I had a collection view with resizable cells that had threw this warning whenever I rotated the device orientation. If a cell was visible after post-rotation but not pre-rotation, the device would still try to snapshot the pre-rotation version of the cell that had yet to be rendered. So I ended up doing a check in my collection view controller to see which cells where visible prior to rotation. This is for vertically scrolling collection views, but you could probably just swap the references to x, y, minX, minY, etc... to get it working for horizontal collection views.

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
        }
    }

}

where custom cell was a UICollectionViewCell subclass with the following:

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
        }
    }
}

I think this has completely gotten rid of it. I couldn't just use all the visible cells returned by collectionView.visibleCells to determine which cells to snapshot, since I was still displaying the log message for cells that were barely on screen (My current hypothesis is that for whatever reason cells in the safe area are not considered rendered).

Tincher answered 23/6, 2019 at 4:42 Comment(0)
B
0

Put the collectionView layoutIfNeeded() function in the DispatchQueue.main.async right after you invalidate the layout.

    let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout

    flowLayout?.invalidateLayout()

    DispatchQueue.main.async { [weak self] in
          self?.collectionView.layoutIfNeeded()
    }
Bactria answered 15/4, 2019 at 0:25 Comment(0)
N
0

Just add to UICollectionView

[pCollectionView snapshotViewAfterScreenUpdates:YES];

It will be looking on Objective-C like:

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];
Nonstriated answered 27/5, 2020 at 5:57 Comment(0)
M
0

I had the messages occurring in the context of trying to render a custom UICollectionViewCell.

I tried @LucasEduardo's answer, adding the following overriding function implementation to my custom UICollectionViewCell class, and it seems to have resolved it. Here's Lucas' original 2015 answer (which was written in objective C) converted to Swift 5.5+ (running on iOS 16+)

override func snapshotView(afterScreenUpdates afterUpdates: Bool) -> UIView? {
    UIGraphicsBeginImageContext(self.bounds.size);
    draw(self.bounds)
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext();
    let imageView = UIImageView(frame: self.bounds)
    imageView.image = image
    return imageView
}
Menderes answered 12/7, 2023 at 21:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.