在旋转接口方向时将contentOffset保留在UICollectionView中

Tob*_*zer 70 cocoa-touch objective-c uiscrollview ios uicollectionview

我正在尝试在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];
}
Run Code Online (Sandbox Code Playgroud)

这会在旋转后更改内容偏移.

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

一个例子可以在我的GitHub项目中找到.

Gur*_*ngh 23

这是Swift 3.1中的代码,同样适用于Swift 4.2

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)
{
    super.viewWillTransition(to: size, with: coordinator)
    let offset = self.collectionView?.contentOffset;
    let width  = self.collectionView?.bounds.size.width;

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

    self.collectionView?.setContentOffset(newOffset, animated: false)


    coordinator.animate(alongsideTransition: { (context) in
        self.collectionView?.reloadData()
        self.collectionView?.setContentOffset(newOffset, animated: false)
    }, completion: nil)
}
Run Code Online (Sandbox Code Playgroud)


fz.*_*fz. 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);
}
Run Code Online (Sandbox Code Playgroud)

但是你要面对的问题是那个过渡的动画非常奇怪.我在我的案子上做的事情(几乎与你的情况相同)是:

解决方案2,"流畅动画"

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

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

请注意,[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];
}
Run Code Online (Sandbox Code Playgroud)

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

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

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


小智 14

上面的"只是快速"回答对我来说不起作用,因为它经常没有在旋转之前的视图中结束.所以我派生了一个流程布局,使用焦点项(如果设置)来计算内容偏移量.我在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
Run Code Online (Sandbox Code Playgroud)


Nei*_*ilc 11

对于使用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
    }];
}
Run Code Online (Sandbox Code Playgroud)

斯威夫特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
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 全屏细胞怎么样?当我滑到第二个或第三个单元格,并旋转我看到最后一个单元格,并持续第二个:( (3认同)

Bog*_*kov 9

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
    }
}
Run Code Online (Sandbox Code Playgroud)


2cu*_*ech 7

我认为正确的解决方案是在子类中覆盖- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset方法UICollectionViewFlowLayout

来自文档:

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


Joe*_*Joe 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
Run Code Online (Sandbox Code Playgroud)


Ken*_*Cal -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];
     }];
}
Run Code Online (Sandbox Code Playgroud)