旋转Interface Orientation时将contentOffset保留在UICollectionView中


74

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

以内容偏移量为{ bounds.size.width * 2,0}的肖像开始...

Portait中的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上的项目中找到一个示例。


7
您找到解决方案了吗?
加尔2013年

Answers:


33

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

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


准确准确的答案。非常感谢!
肯南·贝吉奇(KenanBegić),

经过大量研究,这是最优雅的解决方案!
iDoc

有用。您只需要确保正在调用viewWillTransition。另一点是,如果下一页不是“整个”页面,则只需要舍入索引变量。
内森·巴雷托

您应该使用该floor函数代替round吗?
马里奥(Mario)

@Mario我只使用回合
Gurjinder Singh,

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界限]将根据设备旋转而变化。

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)最后,最后一步是将内容偏移量“捕捉”到有效页面并删除临时imageview。

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
    [self.collectionView setContentOffset:contentOffsetAfterRotation];
    [[self.view viewWithTag:kTemporaryImageTag] removeFromSuperview];
}

1
您的回答很棒!但是您错过了对willRotateToInterfaceOrientation和didRotateFromInterfaceOrientation中的super的调用。
Valeriy Van

这对我不起作用,我改为将其更改为在旋转后不计算偏移量,而是在获取单元格信息时存储拉出的NSIndexPath,而不是设置偏移量而不滚动到没有动画的位置。如果有人感兴趣,我可以将实际答案与代码(快捷方式)放在一起。
mwright 2015年

15

上面的“ just snap”答案对我来说不起作用,因为它通常不会在旋转之前查看的项目上结束。因此,我派生了使用焦点项目(如果已设置)来计算内容偏移量的流布局。我在willAnimateRotationToInterfaceOrientation中设置了该项,并在didRotateFromInterfaceOrientation中将其清除了。在IOS7上似乎需要进行插入调整,因为“收集”视图可以布局在顶部栏中。

@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

到目前为止,最简单的解决方案
只是一名编码人员,

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

3
全屏单元格怎么样?当我滑动到第二个或第三个单元格并旋转时,我看到上一个单元格在最后,第二个单元格上是最后一个:(
iTux

8

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

从文档:

在布局更新期间或在布局之间转换时,集合视图将调用此方法,以使您有机会更改建议的内容偏移以在动画末尾使用。如果动画或过渡可能导致项目以对您的设计而言并非最佳的方式放置,则可以覆盖此方法。


7

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

1
这是我一直在寻找的。我希望将旋转代码包含在我的自定义UICollectionView子类中,这样就无需在整个应用程序中重复旋转代码。但这确实会导致滚动,这意味着重新使用单元格。这可能会或可能不会产生意外的副作用。
丹尼尔·威廉姆斯

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

需要打超级电话吗?
只是一名编码员

我在Xamarin中实现了这一点,在iOS 11中运行良好
Patrick Roberts

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

相当流畅,而且不那么hacky。


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

1
我最终也使用了类似的方法,希望我第一次见到您
-mwright

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

但是在iOS 9中已不赞成使用“ willRotateToInterfaceOrientation”,也可以用不同的方式调用函数[XXXCollectionVew.collectionViewLayout invalidateLayout]。

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


1
请描述您做了什么或告诉您有关项目的任何信息!
Alex Cio 2015年

2

在Swift 3中

您应该在通过indexPath.item,x坐标或其他方法旋转之前,跟踪显示哪个单元格item(Page)。然后,在您的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视图的宽度。


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

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


不要忘记自iOS 8起不推荐使用旋转API]
Daniel Galasko 2014年

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中,但是您明白了,并且很容易“翻译”



0

我的方法是使用UICollectionViewFlowlayout对象。

如果水平滚动,请设置对象的行距。

[flowLayout setMinimumLineSpacing:26.0f];

如果垂直滚动,请设置其项目间距。

[flowLayout setMinimumInteritemSpacing:0.0f];

请注意,旋转屏幕时它的行为有所不同。就我而言,它具有水平滚动的功能,因此minimumlinespacing为26.0f。然后,当它旋转到横向时,这似乎太可怕了。我必须检查旋转并为该方向0.0f设置minimumlinespacing以使其正确。

而已!简单。


0

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

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

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

然后针对每个方向检查它,并针对每个方向使用您的配置。


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

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


0

<CollectionViewDelegateFlowLayout>在方法中使用和didRotateFromInterfaceOrientation:重载CollectionView的数据。

的实现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);

    }

}

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的视图。在超级视图中,我也这样做

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

    [collectionViewFlowLayout setItemSize:frame.size];

    [super setFrame:frame];
}

这是为了将单元格大小调整为全屏(确切地说是全屏;))。如果您不执行此操作,则可能会出现许多错误消息,提示单元格大小大于collectionview,并且此行为未定义,等等。

当然,这些to方法可以合并到collectionview的一个子类中,也可以合并到包含collectionview的视图中,但是对于我当前的项目而言,这是顺理成章的方法。


0

“ just snap”答案是正确的方法,不需要对快照叠加层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);
}

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

我希望这有帮助。


0

我在使用animateAlongsideTransitionblock时遇到了一些麻烦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];
    }];
}

而且我发现另一个有趣的事情是过时方法willRotateToInterfaceOrientationwillRotateToInterfaceOrientation仍然成功调用的iOS 8.0以后的方法时,viewWillTransitionToSize不重新定义。

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


-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];
     }];
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.