74

我正在尝试处理 UICollectionViewController 中的界面方向更改。我想要实现的是,我希望在界面旋转后拥有相同的 contentOffset 。意思是,它应该根据边界变化的比率而改变。

从纵向开始,内容偏移量为 { bounds.size.width * 2, 0} ...</p>

肖像中的 UICollectionView

...应该导致横向内容偏移也与 { bounds.size.width * 2, 0} (反之亦然)。

UICollectionView 在横向

计算新的偏移量不是问题,但不知道在哪里(或何时)设置它,以获得平滑的动画。我所做的是使布局无效willRotateToInterfaceOrientation:duration:并重置内容偏移量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];
}

这会更改旋转后的内容偏移量。

如何在旋转期间设置它?我试图设置新的内容偏移量,willAnimateRotationToInterfaceOrientation:duration:但这会导致非常奇怪的行为。

可以在我的GitHub项目中找到一个示例。

4

24 回答 24

38

您可以在视图控制器中执行此操作:

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

    guard let collectionView = collectionView else { return }
    let offset = collectionView.contentOffset
    let width = collectionView.bounds.size.width

    let index = round(offset.x / width)
    let newOffset = CGPoint(x: index * size.width, y: offset.y)

    coordinator.animate(alongsideTransition: { (context) in
        collectionView.reloadData()
        collectionView.setContentOffset(newOffset, animated: false)
    }, completion: nil)
}

或者在布局本身:https ://stackoverflow.com/a/54868999/308315

于 2017-04-10T12:05:17.093 回答
18

解决方案 1,“快拍”

如果您只需要确保 contentOffset 以正确的位置结束,您可以创建 UICollectionViewLayout 的子类并实现targetContentOffsetForProposedContentOffset:方法。例如,您可以执行以下操作来计算页面:

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
{
    NSInteger page = ceil(proposedContentOffset.x / [self.collectionView frame].size.width);
    return CGPointMake(page * [self.collectionView frame].size.width, 0);
}

但是您将面临的问题是该过渡的动画非常奇怪。我对我的案子(几乎和你的案子一样)正在做的是:

解决方案2,“流畅的动画”

1)首先我设置单元格大小,可以通过collectionView:layout:sizeForItemAtIndexPath:委托方法管理如下:

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

请注意,[self.view bounds] 将根据设备旋转而改变。

2) 当设备即将旋转时,我在集合视图的顶部添加了一个带有所有调整大小掩码的 imageView。这个视图实际上会隐藏 collectionView 的怪异(因为它在它之上),并且由于 willRotatoToInterfaceOrientation: 方法在动画块内被调用,它会相应地旋转。我还根据显示的 indexPath 保留下一个 contentOffset,以便在旋转完成后修复 contentOffset:

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
                                duration:(NSTimeInterval)duration
{
    // Gets the first (and only) visible cell.
    NSIndexPath *indexPath = [[self.collectionView indexPathsForVisibleItems] firstObject];
    KSPhotoViewCell *cell = (id)[self.collectionView cellForItemAtIndexPath:indexPath];

    // Creates a temporary imageView that will occupy the full screen and rotate.
    UIImageView *imageView = [[UIImageView alloc] initWithImage:[[cell imageView] image]];
    [imageView setFrame:[self.view bounds]];
    [imageView setTag:kTemporaryImageTag];
    [imageView setBackgroundColor:[UIColor blackColor]];
    [imageView setContentMode:[[cell imageView] contentMode]];
    [imageView setAutoresizingMask:0xff];
    [self.view insertSubview:imageView aboveSubview:self.collectionView];

    // Invalidate layout and calculate (next) contentOffset.
    contentOffsetAfterRotation = CGPointMake(indexPath.item * [self.view bounds].size.height, 0);
    [[self.collectionView collectionViewLayout] invalidateLayout];
}

请注意,我的 UICollectionViewCell 子类具有公共 imageView 属性。

3)最后,最后一步是将内容偏移“捕捉”到有效页面并删除临时图像视图。

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
    [self.collectionView setContentOffset:contentOffsetAfterRotation];
    [[self.view viewWithTag:kTemporaryImageTag] removeFromSuperview];
}
于 2014-03-11T20:25:04.273 回答
15

上面的“快速捕捉”答案对我不起作用,因为它经常没有在旋转之前看到的项目上结束。所以我派生了一个使用焦点项目(如果设置)来计算内容偏移量的流布局。我在 willAnimateRotationToInterfaceOrientation 中设置项目并在 didRotateFromInterfaceOrientation 中清除它。在 IOS7 上似乎需要进行插图调整,因为 Collection 视图可以布局在顶部栏下。

@interface HintedFlowLayout : UICollectionViewFlowLayout
@property (strong)NSIndexPath* pathForFocusItem;
@end

@implementation HintedFlowLayout

-(CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
{
    if (self.pathForFocusItem) {
        UICollectionViewLayoutAttributes* layoutAttrs = [self layoutAttributesForItemAtIndexPath:self.pathForFocusItem];
        return CGPointMake(layoutAttrs.frame.origin.x - self.collectionView.contentInset.left, layoutAttrs.frame.origin.y-self.collectionView.contentInset.top);
    }else{
        return [super targetContentOffsetForProposedContentOffset:proposedContentOffset];
    }
}
@end
于 2014-03-18T20:58:32.093 回答
10

对于使用 iOS 8+ 的用户,不推荐使用willRotateToInterfaceOrientationdidRotateFromInterfaceOrientation

您现在应该使用以下内容:

/* 
This method is called when the view controller's view's size is changed by its parent (i.e. for the root view controller when its window rotates or is resized). 
If you override this method, you should either call super to propagate the change to children or manually forward the change to children.
*/
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator 
{
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];

    [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
        // Update scroll position during rotation animation
        self.collectionView.contentOffset = (CGPoint){contentOffsetX, contentOffsetY};
    } completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
        // Whatever you want to do when the rotation animation is done
    }];
}

斯威夫特 3:

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

    coordinator.animate(alongsideTransition: { (context:UIViewControllerTransitionCoordinatorContext) in
        // Update scroll position during rotation animation
    }) { (context:UIViewControllerTransitionCoordinatorContext) in
        // Whatever you want to do when the rotation animation is done
    }
}
于 2015-03-16T14:57:03.757 回答
10

Swift 4.2 子类:

class RotatableCollectionViewFlowLayout: UICollectionViewFlowLayout {

    private var focusedIndexPath: IndexPath?

    override func prepare(forAnimatedBoundsChange oldBounds: CGRect) {
        super.prepare(forAnimatedBoundsChange: oldBounds)
        focusedIndexPath = collectionView?.indexPathsForVisibleItems.first
    }

    override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
        guard let indexPath = focusedIndexPath
            , let attributes = layoutAttributesForItem(at: indexPath)
            , let collectionView = collectionView else {
                return super.targetContentOffset(forProposedContentOffset: proposedContentOffset)
        }
        return CGPoint(x: attributes.frame.origin.x - collectionView.contentInset.left,
                       y: attributes.frame.origin.y - collectionView.contentInset.top)
    }

    override func finalizeAnimatedBoundsChange() {
        super.finalizeAnimatedBoundsChange()
        focusedIndexPath = nil
    }
}
于 2019-02-25T15:00:07.233 回答
8

我认为正确的解决方案是覆盖targetContentOffsetForProposedContentOffset:子类中的方法UICollectionViewFlowLayout

从文档:

在布局更新期间,或在布局之间转换时,集合视图会调用此方法,让您有机会更改建议的内容偏移以在动画结束时使用。如果动画或过渡可能导致项目以不适合您的设计的方式定位,您可以覆盖此方法。

于 2014-03-08T04:57:53.400 回答
5

背负 troppoli 的解决方案,您可以在自定义类中设置偏移量,而不必担心记住在视图控制器中实现代码。prepareForAnimatedBoundsChange应该在您旋转设备然后finalizeAnimatedBoundsChange在其完成旋转后调用。

@interface OrientationFlowLayout ()

@property (strong)NSIndexPath* pathForFocusItem;

@end

@implementation OrientationFlowLayout

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset {
    if (self.pathForFocusItem) {
        UICollectionViewLayoutAttributes* layoutAttrs = [self layoutAttributesForItemAtIndexPath:
                                                         self.pathForFocusItem];
        return CGPointMake(layoutAttrs.frame.origin.x - self.collectionView.contentInset.left,
                           layoutAttrs.frame.origin.y - self.collectionView.contentInset.top);
    }
    else {
        return [super targetContentOffsetForProposedContentOffset:proposedContentOffset];
    }
}

- (void)prepareForAnimatedBoundsChange:(CGRect)oldBounds {
    [super prepareForAnimatedBoundsChange:oldBounds];
    self.pathForFocusItem = [[self.collectionView indexPathsForVisibleItems] firstObject];
}

- (void)finalizeAnimatedBoundsChange {
    [super finalizeAnimatedBoundsChange];
    self.pathForFocusItem = nil;
}

@end
于 2016-10-03T23:40:04.943 回答
3

这个问题也困扰了我一点。投票最高的答案对我来说似乎有点太老套了,所以我只是把它简化了一点,然后分别在旋转之前和之后更改集合视图的 alpha。我也不会为内容偏移更新设置动画。

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
self.collectionView.alpha = 0;
[self.collectionView.collectionViewLayout invalidateLayout];

self.scrollPositionBeforeRotation = CGPointMake(self.collectionView.contentOffset.x / self.collectionView.contentSize.width,
                                                self.collectionView.contentOffset.y / self.collectionView.contentSize.height);
}

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

[self.collectionView setContentOffset:newContentOffset animated:NO];
self.collectionView.alpha = 1;
}

相当流畅,不那么骇人听闻。

于 2014-11-13T14:45:12.560 回答
3

我使用 fz 的变体。答案(iOS 7 和 8):

旋转前:

  1. 存储当前可见索引路径
  2. 创建collectionView的快照
  3. 将 UIImageView 与它放在集合视图的顶部

旋转后:

  1. 滚动到存储的索引
  2. 删除图像视图。

    @property (nonatomic) NSIndexPath *indexPath;
    
    - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
                                    duration:(NSTimeInterval)duration {
        self.indexPathAfterRotation = [[self.collectionView indexPathsForVisibleItems] firstObject];
    
        // Creates a temporary imageView that will occupy the full screen and rotate.
        UIGraphicsBeginImageContextWithOptions(self.collectionView.bounds.size, YES, 0);
        [self.collectionView drawViewHierarchyInRect:self.collectionView.bounds afterScreenUpdates:YES];
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    
        UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
        [imageView setFrame:[self.collectionView bounds]];
        [imageView setTag:kTemporaryImageTag];
        [imageView setBackgroundColor:[UIColor blackColor]];
        [imageView setContentMode:UIViewContentModeCenter];
        [imageView setAutoresizingMask:0xff];
        [self.view insertSubview:imageView aboveSubview:self.collectionView];
    
        [[self.collectionView collectionViewLayout] invalidateLayout];
    }
    
    - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
        [self.collectionView scrollToItemAtIndexPath:self.indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
    
        [[self.view viewWithTag:kTemporaryImageTag] removeFromSuperview];
    }
    
于 2015-04-29T08:20:45.090 回答
2

在斯威夫特 3。

您应该在通过 indexPath.item、x 坐标或其他东西旋转之前跟踪正在呈现的单元格项目(页面)。然后,在您的 UICollectionView 中:

override func collectionView(_ collectionView: UICollectionView, targetContentOffsetForProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {

    let page:CGFloat = pageNumber // your tracked page number eg. 1.0
    return CGPoint(x: page * collectionView.frame.size.width, y: -(topInset))
    //the 'y' value would be '0' if you don't have any top EdgeInset
}

就我而言,我使 viewDidLayoutSubviews() 中的布局无效,因此 collectionView.frame.size.width 是已旋转的 collectionVC 视图的宽度。

于 2016-12-28T11:40:37.647 回答
2

旋转界面方向后,UICollectionViewCell 通常会移动到另一个位置,因为我们不会更新 contentSize 和 contentOffset。

所以可见的 UICollectionViewCell 总是不在预期的位置。

我们期望的可见 UICollectionView 图像如下

我们期望的方向

UICollectionView 必须委托『UICollectionViewDelegateFlowLayout』的函数[collectionView sizeForItemAtIndexPath]。

您应该在此函数中计算项目大小。

自定义 UICollectionViewFlowLayout 必须覆盖以下函数。

  1. -(void)prepareLayout

    .设置 itemSize、scrollDirection 等。

  2. -(CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity

    .计算页码或计算可见内容偏移量。

  3. -(CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset

    .返回视觉内容偏移量。

  4. -(CGSize)collectionViewContentSize

    .返回 collectionView 的总内容大小。

你的 viewController 必须覆盖『willRotateToInterfaceOrientation』并且在这个函数中你应该调用函数[XXXCollectionVew.collectionViewLayout invalidateLayout];

但是『willRotateToInterfaceOrientation』在 iOS 9 中被弃用了,或者你可以用不同的方式调用函数 [XXXCollectionVew.collectionViewLayout invalidateLayout]。

有一个例子如下: https ://github.com/bcbod2002/CollectionViewRotationTest

于 2015-10-26T06:01:15.583 回答
1

如果发现使用 targetContentOffsetForProposedContentOffset 并不适用于所有场景,并且使用 didRotateFromInterfaceOrientation 的问题是它会产生视觉伪影。我完美的工作代码如下:

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
    _indexPathOfFirstCell = [self indexPathsForVisibleItems].firstObject;
}

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
    [super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
    if (_indexPathOfFirstCell) {
        [UIView performWithoutAnimation:^{
            [self scrollToItemAtIndexPath:self->_indexPathOfFirstCell atScrollPosition:UICollectionViewScrollPositionTop animated:NO];
        }];
        _indexPathOfFirstCell = nil;
    }
}

关键是使用 willRotateToInterfaceOrientation 方法确定视图中要滚动到的部分,并在视图更改其大小时重新计算它(框架调用此方法时边界已经更改)并实际上滚动到没有动画的新位置。在我的代码中,我使用第一个可视单元格的索引路径来执行此操作,但是 contentOffset.y/contentSize.height 的百分比也会以稍微不同的方式完成这项工作。

于 2014-11-25T20:38:58.790 回答
1

我的工作是这样的:

  1. 从您的我的UICollectionViewDelegateFlowLayout方法设置您的我的单元格的大小

    func collectionView(collectionView: UICollectionView!, layout collectionViewLayout: UICollectionViewLayout!, sizeForItemAtIndexPath indexPath: NSIndexPath!) -> CGSize
    {
        return collectionView.bounds.size
    }
    
  2. 之后我willRotateToInterfaceOrientationToInterfaceOrientation:duration:像这样实现

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

上面的代码是用Swift编写的,但你明白了,而且很容易“翻译”

于 2014-06-15T14:37:48.450 回答
1

这项工作就像一个魅力:

-(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];
}];
}
于 2014-11-12T13:03:43.700 回答
0

我有一个类似的案例,我用这个

- (void)setFrame:(CGRect)frame
{
    CGFloat currentWidth = [self frame].size.width;
    CGFloat offsetModifier = [[self collectionView] contentOffset].x / currentWidth;

    [super setFrame:frame];

    CGFloat newWidth = [self frame].size.width;

    [[self collectionView] setContentOffset:CGPointMake(offsetModifier * newWidth, 0.0f) animated:NO];
}

这是一个包含 collectionView 的视图。在superview中我也这样做

- (void)setFrame:(CGRect)frame
{    
    UICollectionViewFlowLayout *collectionViewFlowLayout = (UICollectionViewFlowLayout *)[_collectionView collectionViewLayout];

    [collectionViewFlowLayout setItemSize:frame.size];

    [super setFrame:frame];
}

这是将单元格大小调整为全屏(确切地说是全视图;))。如果您不在这里执行此操作,则可能会出现很多错误消息,说明单元格大小大于集合视图,并且未定义此行为并且 bla bla bla .....

这些 to 方法当然可以合并到 collectionview 的一个子类或包含 collectionview 的视图中,但对于我当前的项目来说,这是合乎逻辑的方式。

于 2014-02-27T08:44:32.147 回答
0

我的方法是使用 UICollectionViewFlowlayout 对象。

如果它水平滚动,则设置对象行间距。

[flowLayout setMinimumLineSpacing:26.0f];

如果它垂直滚动,则设置它的项间间距。

[flowLayout setMinimumInteritemSpacing:0.0f];

请注意,当您旋转屏幕时,它的行为会有所不同。就我而言,我让它水平滚动,所以 minimumlinespacing 是 26.0f。然后当它旋转到横向时看起来很可怕。我必须检查旋转并将该方向的最小线间距设置为 0.0f 以使其正确。

而已!简单的。

于 2013-11-28T13:06:05.587 回答
0

我的项目有问题,我为 UICollectionView 使用了两种不同的布局。

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

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

然后检查每个方向并为每个方向使用您的配置。

于 2013-12-05T06:26:42.070 回答
0
-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    CGSize pnt = CGSizeMake(70, 70);
    return pnt; }

-(UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {

//    UIEdgeInsetsMake(<#CGFloat top#>, <#CGFloat left#>, <#CGFloat bottom#>, <#CGFloat right#>)
    return UIEdgeInsetsMake(3, 0, 3, 0); }

这样您就可以调整内容偏移量和单元格的大小。

于 2013-12-05T17:12:02.313 回答
0

在CollectionView<CollectionViewDelegateFlowLayout>的方法reload data中使用and。didRotateFromInterfaceOrientation:

实现collectionView:layout:sizeForItemAtIndexPath:方法<CollectionViewDelegateFlowLayout>并在方法中验证界面方向并应用您自定义的每个单元格的大小。

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{

    UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];

    if (UIInterfaceOrientationIsPortrait(orientation)) {

        return CGSizeMake(CGFloat width, CGFloat height);

    } else {

        return CGSizeMake(CGFloat width, CGFloat height);

    }

}
于 2014-02-03T06:06:09.023 回答
0

您可能希望在它的(不正确的)动画期间隐藏 collectionView 并显示正确旋转的单元格的占位符视图。

对于一个简单的照片库,我找到了一种看起来相当不错的方法。在这里查看我的答案: 如何旋转类似于照片应用程序的 UICollectionView 并保持当前视图居中?

于 2013-09-24T21:00:50.577 回答
0

“只是快照”的答案是正确的方法,不需要使用快照覆盖 IMO 进行额外的平滑处理。但是,有一个问题可以解释为什么有些人看到在某些情况下没有滚动到正确的页面。在计算页面时,您希望使用高度而不是宽度。为什么?因为在调用 targetContentOffsetForProposedContentOffset 时视图几何图形已经旋转,所以宽度现在是高度。舍入也比天花板更明智。所以:

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
{
    NSInteger page = round(proposedContentOffset.x / self.collectionView.bounds.size.height);
    return CGPointMake(page * self.collectionView.bounds.size.width, 0);
}
于 2015-11-04T18:13:17.590 回答
0

我遇到了一些animateAlongsideTransition阻塞问题animateAlongsideTransition(请参见下面的代码)。

请注意,它在动画期间(但不是之前)被调用我的任务是使用滚动到顶部可见行来更新表格视图滚动位置(当设备上的表格视图单元格向上移动时,我遇到了 iPad旋转,因此我正在为该问题找到解决方案)。但它可能对 contentOffset 也有用。

我尝试通过以下方式解决问题:

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

但它没有用。例如,顶部 cel 的索引路径是 (0, 20)。但是当调用设备旋转 animateAlongsideTransition 块并且 [[weakSelf.tableView indexPathsForVisibleRows] firstObject] 返回索引路径 (0, 27)。

我认为问题在于检索weakSelf. 因此,为了解决我在方法调用self.topVisibleRowIndexPath之前移动的问题:[coordinator animateAlongsideTransition: completion]

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

我发现的另一件有趣的事情是,在未重新定义方法时,已弃用的方法在 iOS 8.0 以后的版本willRotateToInterfaceOrientationwillRotateToInterfaceOrientation仍然可以成功调用。viewWillTransitionToSize

因此,在我的情况下解决问题的另一种方法是使用不推荐使用的方法而不是新方法。我认为这不是正确的解决方案,但如果其他方法不起作用,可以尝试:)

于 2018-09-06T09:25:36.890 回答
0

我通过以下步骤解决了这个问题:

  1. 计算当前滚动的 NSIndexPath
  2. 在 UICollectionView 中禁用滚动和分页
  3. 将新的流布局应用到 UICollectionView
  4. 在 UICollectionView 中启用滚动和分页
  5. 滚动 UICollectionView 到当前的 NSIndexPath

这是演示上述步骤的代码模板:

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
                            duration:(NSTimeInterval)duration;
{
     //Calculating Current IndexPath
     CGRect visibleRect = (CGRect){.origin = self.yourCollectionView.contentOffset, .size = self.yourCollectionView.bounds.size};
     CGPoint visiblePoint = CGPointMake(CGRectGetMidX(visibleRect), CGRectGetMidY(visibleRect));
     self.currentIndexPath = [self.yourCollectionView indexPathForItemAtPoint:visiblePoint];

     //Disable Scrolling and Pagination
     [self disableScrolling];

     //Applying New Flow Layout
     [self setupNewFlowLayout];

     //Enable Scrolling and Pagination
     [self enableScrolling];
}

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation;
{
     //You can also call this at the End of `willRotate..` method.
     //Scrolling UICollectionView to current Index Path
     [self.yourCollectionView scrollToItemAtIndexPath:self.currentIndexPath atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:NO];
}

- (void) disableScrolling
{
    self.yourCollectionView.scrollEnabled   = false;
    self.yourCollectionView.pagingEnabled   = false;
}

- (void) enableScrolling
{
    self.yourCollectionView.scrollEnabled   = true;
    self.yourCollectionView.pagingEnabled   = true;
}

- (void) setupNewFlowLayout
{
    UICollectionViewFlowLayout* flowLayout = [[UICollectionViewFlowLayout alloc] init];
    flowLayout.sectionInset = UIEdgeInsetsMake(0, 0, 0, 0);
    flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    flowLayout.minimumInteritemSpacing = 0;
    flowLayout.minimumLineSpacing = 0;
    [flowLayout setItemSize:CGSizeMake(EXPECTED_WIDTH, EXPECTED_HEIGHT)];

    [self.yourCollectionView setCollectionViewLayout:flowLayout animated:YES];
    [self.yourCollectionView.collectionViewLayout invalidateLayout];
}

我希望这有帮助。

于 2016-04-14T12:16:45.407 回答
-2

您可能想试试这个未经测试的代码:

- (void) willRotateToInterfaceOrientation: (UIInterfaceOrientation) toInterfaceOrientation
                                 duration: (NSTimeInterval)         duration
{
    [UIView animateWithDuration: duration
                      animation: ^(void)
     {
       CGPoint newContentOffset = CGPointMake(self.scrollPositionBeforeRotation.x *
                                              self.collectionView.contentSize.height,
                                              self.scrollPositionBeforeRotation.y *
                                              self.collectionView.contentSize.width);
       [self.collectionView setContentOffset: newContentOffset
                                    animated: YES];
     }];
}
于 2012-12-08T17:20:56.093 回答