UICollectionViewFlowLayout not invalidating right

2020-02-17 09:34发布

i have a UICollectionView with a UICollectionViewFlowLayout. I also implement the UICollectionViewDelegateFlowLayout protocol.

In my datasource i have a bunch of UIViewControllers which respond to a custom protocol so i can ask for their size and some other stuff.

In in the FlowLayout delegate when it asks for the sizeForItemAtIndexPath:, i return the item size which i get from my protocol. The ViewControllers which implement my protocol return a different item size depending on the orientation.

Now if i change the device orientation from portrait to landscape theres no problem (Items are larger in landscape) but if i change it back, i get this warning:

    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.

It still works but i don't like it to get warnings so maybe you can tell me what i am doing wrong. Also there is another problem. If i don't tell my collectionViews collectionViewLayout to invalidate in willAnimateRotationToInterfaceOrientation: the sizeForItemAtIndexPath: is never called.

Hope you understand what i mean. If you need additional information let me know :)

7条回答
forever°为你锁心
2楼-- · 2020-02-17 09:53

This answer is late, but the accepted answer didn't work for me. I sympathize with the OP in wanting a fire-and-forget UICollectionViewFlowLayout. I suggest that invalidating the layout in the view controller is in fact the best solution.

I wanted a single horizontal scrolling line of cells, centered in the view, in both portrait and landscape.

I subclassed UICollectionViewFlowLayout.

I overrode prepareLayout to recalculate the insets and then call [super prepareLayout].

I overrode the getter for collectionViewContentSize to make certain the content size was correct.

The layout didn't invalidate on its own even though the bounds were changing with the reorientation.

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

The UICollectionViewFlowLayout maintains the scroll position between orientations. The same cell will be centered only if it is square.

查看更多
Evening l夕情丶
3楼-- · 2020-02-17 09:54

The @0mahc0's answer worked for me, but only partially.

I don't have the rotate problem, because I force my app be only in Portrait mode, but my solution can help others with the same problem.

The warning shows every time the view appears. The reason of the warning is very clear, we have defined section insets to left and right of UICollectionView. Check interface builder and you will see in metrics those values defined.

The @0mahc0's solution works, but I want to stretch the cell to max width, so I removed the insets of section, otherwise I will have a 'margin' left and right.

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

Instead of implementing the delegate method you can go to interface builder and change value to zero of right and left insets.

查看更多
The star\"
4楼-- · 2020-02-17 09:58

One solution is to use KVO and listen for a change to the frame of the superview for your collection view. Calling -reloadData will work and get rid of the warnings. There is an underlying timing issue here...

// -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];
}
查看更多
Rolldiameter
5楼-- · 2020-02-17 10:06

iOS8: the only way I could silence the warning is to call invalidate during the transition when rotating to a larger size and before transition when rotating to a smaller size.

    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) {
    }];
查看更多
狗以群分
6楼-- · 2020-02-17 10:10

I used a combination of the two answers here to do what we are all trying to do really is make a collection view look like a table view but not use UITableView and get items in a single column (most likely)

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

    [self.collectionView.collectionViewLayout invalidateLayout];
}
查看更多
来,给爷笑一个
7楼-- · 2020-02-17 10:12

I don't know if it can help to someone but none of the last answer help me because I'm using some UIViews into a UIScrollView.

I'm opening the UIViews programmatically depending on some events. So I can't use - (void) willRotateToInterfaceOrientation: and invalidateLayout or reloadData because:
1. invalidateLayout "occurs during the next view layout update cycle."
2. reloadData "For efficiency, the collection view only displays those cells and supplementary views that are visible."

What worked for me was use the width of the collectionView's parent (an UIView) in:

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


For some reason the collectionView frame had the width of portrait at the time to call this function... using int currentWidth = self.frame.width help me to fix it.

查看更多
登录 后发表回答