Keeping the contentOffset in a UICollectionView wh

2019-01-12 19:15发布

I'm trying to handle interface orientation changes in a UICollectionViewController. What I'm trying to achieve is, that I want to have the same contentOffset after an interface rotation. Meaning, that it should be changed corresponding to the ratio of the bounds change.

Starting in portrait with a content offset of {bounds.size.width * 2, 0} …

UICollectionView in portait

… should result to the content offset in landscape also with {bounds.size.width * 2, 0} (and vice versa).

UICollectionView in landscape

Calculating the new offset is not the problem, but don't know, where (or when) to set it, to get a smooth animation. What I'm doing so fare is invalidating the layout in willRotateToInterfaceOrientation:duration: and resetting the content offset in didRotateFromInterfaceOrientation::

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
                                duration:(NSTimeInterval)duration;
{
    self.scrollPositionBeforeRotation = CGPointMake(self.collectionView.contentOffset.x / self.collectionView.contentSize.width,
                                                    self.collectionView.contentOffset.y / self.collectionView.contentSize.height);
    [self.collectionView.collectionViewLayout invalidateLayout];
}

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation;
{
    CGPoint newContentOffset = CGPointMake(self.scrollPositionBeforeRotation.x * self.collectionView.contentSize.width,
                                           self.scrollPositionBeforeRotation.y * self.collectionView.contentSize.height);
    [self.collectionView newContentOffset animated:YES];
}

This changes the content offset after the rotation.

How can I set it during the rotation? I tried to set the new content offset in willAnimateRotationToInterfaceOrientation:duration: but this results into a very strange behavior.

An example can be found in my Project on GitHub.

23条回答
【Aperson】
2楼-- · 2019-01-12 19:50

I had got some troubles with animateAlongsideTransition block in animateAlongsideTransition (see the code below).

Pay attention, that it is called during (but not before) the animation My task was update the table view scroll position using scrolling to the top visible row (I’ve faced with the problem on iPad when table view cells shifted up when the device rotation, therefore I was founding the solution for that problem). But may be it would be useful for contentOffset too.

I tried to solve the problem by the following way:

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

    __weak TVChannelsListTableViewController *weakSelf = self;

    [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
        weakSelf.topVisibleRowIndexPath = [[weakSelf.tableView indexPathsForVisibleRows] firstObject];
    } completion:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
        [weakSelf.tableView scrollToRowAtIndexPath:weakSelf.topVisibleRowIndexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
    }];
}

But it didn’t work. For instance, index path of the top cel was (0, 20). But when the device rotation animateAlongsideTransition block was called and [[weakSelf.tableView indexPathsForVisibleRows] firstObject] returned index path (0, 27).

I thought the problem was in retrieving index paths to weakSelf. Therefore to solve the problem I’ve moved self.topVisibleRowIndexPath before [coordinator animateAlongsideTransition: completion] method calling:

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

    __weak TVChannelsListTableViewController *weakSelf = self;
    self.topVisibleRowIndexPath = [[weakSelf.tableView indexPathsForVisibleRows] firstObject];

    [coordinator animateAlongsideTransition:nil completion:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
        [weakSelf.tableView scrollToRowAtIndexPath:weakSelf.topVisibleRowIndexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
    }];
}

And the other interesting thing that I’ve discovered is that the deprecated methods willRotateToInterfaceOrientation and willRotateToInterfaceOrientation are still successful called in iOS later 8.0 when method viewWillTransitionToSize is not redefined.

So the other way to solve the problem in my case was to use deprecated method instead of new one. I think it would be not right solution, but it is possible to try if other ways don’t work :)

查看更多
Ridiculous、
3楼-- · 2019-01-12 19:54

This work like a charm:

-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    return self.view.bounds.size;
}

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

    int currentPage = collectionMedia.contentOffset.x / collectionMedia.bounds.size.width;
    float width = collectionMedia.bounds.size.height;

    [UIView animateWithDuration:duration animations:^{
        [self.collectionMedia setContentOffset:CGPointMake(width * currentPage, 0.0) animated:NO];
        [[self.collectionMedia collectionViewLayout] invalidateLayout];
}];
}
查看更多
▲ chillily
4楼-- · 2019-01-12 19:54

What does the job for me is this:

  1. Set the size of your my cells from your my UICollectionViewDelegateFlowLayout method

    func collectionView(collectionView: UICollectionView!, layout collectionViewLayout: UICollectionViewLayout!, sizeForItemAtIndexPath indexPath: NSIndexPath!) -> CGSize
    {
        return collectionView.bounds.size
    }
    
  2. After that I implement willRotateToInterfaceOrientationToInterfaceOrientation:duration: like this

    override func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) 
    {
        let currentPage = Int(collectionView.contentOffset.x / collectionView.bounds.size.width)
    
        var width = collectionView.bounds.size.height
        UIView.animateWithDuration(duration) {
            self.collectionView.setContentOffset(CGPointMake(width * CGFloat(currentPage), 0.0), animated: false)
            self.collectionView.collectionViewLayout.invalidateLayout()
        }
    }
    

The above code is in Swift but you get the point and it's easy to "translate"

查看更多
够拽才男人
5楼-- · 2019-01-12 19:54

I had the issue with my project,i used two different layout for the UICollectionView.

mCustomCell *cell = [cv dequeueReusableCellWithReuseIdentifier:@"LandScapeCell" forIndexPath:indexPath];

theCustomCell *cell = [cv dequeueReusableCellWithReuseIdentifier:@"PortraitCell" forIndexPath:indexPath];

Then Check it for each orientation and use your configuration for each orientation.

查看更多
够拽才男人
6楼-- · 2019-01-12 19:55

The "just snap" answer is the right approach and doesn't require extra smoothing with snapshot overlays IMO. However there's an issue which explains why some people see that the correct page isn't scrolled to in some cases. When calculating the page, you'd want to use the height and not the width. Why? Because the view geometry has already rotated by the time targetContentOffsetForProposedContentOffset is called, and so what was the width is now the height. Also rounding is more sensible than ceiling. So:

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
{
    NSInteger page = round(proposedContentOffset.x / self.collectionView.bounds.size.height);
    return CGPointMake(page * self.collectionView.bounds.size.width, 0);
}
查看更多
登录 后发表回答