【问题标题】:Keeping the contentOffset in a UICollectionView while rotating Interface Orientation在旋转界面方向时将 contentOffset 保留在 UICollectionView 中
【发布时间】:2012-11-09 12:22:19
【问题描述】:

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

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

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

计算新的偏移量不是问题,但不知道在哪里(或何时)设置它,以获得平滑的动画。我所做的是使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上的我的项目中找到一个示例。

【问题讨论】:

  • 您找到解决方案了吗?

标签: ios objective-c cocoa-touch uiscrollview uicollectionview


【解决方案1】:

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

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

【讨论】:

  • 准确而准确的答案。非常感谢!
  • 经过大量研究,这是最优雅的解决方案!
  • 它有效。你只需要确保 viewWillTransition 被调用。另一点是,如果下一页不是“整个”页面,您只需要对索引变量进行一轮。
  • 你应该使用floor函数而不是round吗?
  • @Mario 我只使用圆形
【解决方案2】:

解决方案 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];
}

【讨论】:

  • 你的回答太棒了!但是你在 willRotateToInterfaceOrientation 和 didRotateFromInterfaceOrientation 中错过了对 super 的呼叫。
  • 这对我不起作用,我改为将其更改为不计算旋转后的偏移量,而是存储在获取单元格信息时拉取的 NSIndexPath,而不是设置滚动到的偏移量没有动画。如果有人有兴趣,我可以用代码(swift)组合一个实际的答案。
【解决方案3】:

上面的“快速捕捉”答案对我不起作用,因为它经常没有在轮换之前看到的项目上结束。所以我派生了一个流布局,它使用焦点项目(如果设置)来计算内容偏移量。我在 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

【讨论】:

  • 迄今为止最简单的解决方案
【解决方案4】:

对于那些使用 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
    }
}

【讨论】:

  • 全屏单元格怎么样?当我滑动到第二个或第三个单元格并旋转时,我看到最后一个单元格和最后一个单元格:(
【解决方案5】:

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

【讨论】:

  • 这正是我想要的。我希望将旋转代码包含在我的自定义 UICollectionView 子类中,这样就不需要在整个应用程序中复制旋转代码。不过,这确实会导致滚动发生,这意味着单元格重用开始了。这可能会或可能不会有无意的副作用。
  • 不幸的是,targetContentOffset 不适合我
【解决方案6】:

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

来自文档:

在布局更新期间,或在布局之间转换时, 集合视图调用这个方法给你机会 更改建议的内容偏移以在动画结束时使用。 如果动画或过渡可能会覆盖此方法 导致物品以不适合您的方式放置 设计。

【讨论】:

    【解决方案7】:

    要背负troppoli's 解决方案,您可以在自定义类中设置偏移量,而不必担心记住在视图控制器中实现代码。 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
    

    【讨论】:

    • 是否需要调用 super ?
    • 我在 Xamarin 中实现了这个,在 iOS 11 中效果很好
    【解决方案8】:

    这个问题也困扰了我一点。投票最高的回答对我来说似乎有点太老套了,所以我只是把它简化了一点,分别在旋转之前和之后更改集合视图的 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;
    }
    

    相当流畅,不那么老套。

    【讨论】:

      【解决方案9】:

      我使用 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];
        }
        

      【讨论】:

      • 我最终也使用了类似的东西,希望我能先看到你的
      【解决方案10】:

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

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

      我们期望的可见 UICollectionView 如下图

      Orientation which we expected

      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

      【讨论】:

      • 请描述你做了什么或者告诉你关于你的项目的任何事情!
      【解决方案11】:

      在 Swift 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 视图的宽度。

      【讨论】:

        【解决方案12】:

        这项工作就像一个魅力:

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

        【讨论】:

          【解决方案13】:

          如果发现使用 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 的百分比也会以稍微不同的方式完成这项工作。

          【讨论】:

          • 不要忘记旋转 API 在 iOS 8 中已被弃用]
          【解决方案14】:

          我的工作是这样的:

          1. 通过您的 myUICollectionViewDelegateFlowLayout 方法设置您的 my 单元格的大小

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

          【讨论】:

            【解决方案15】:

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

            对于一个简单的照片库,我找到了一种看起来相当不错的方法。在这里查看我的答案: How to rotate a UICollectionView similar to the photos app and keep the current view centered?

            【讨论】:

              【解决方案16】:

              我的方法是使用 UICollectionViewFlowlayout 对象。

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

              [flowLayout setMinimumLineSpacing:26.0f];
              

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

              [flowLayout setMinimumInteritemSpacing:0.0f];
              

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

              就是这样!很简单。

              【讨论】:

                【解决方案17】:

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

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

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

                【讨论】:

                  【解决方案18】:
                  -(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); }
                  

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

                  【讨论】:

                    【解决方案19】:

                    使用&lt;CollectionViewDelegateFlowLayout&gt; 并在方法didRotateFromInterfaceOrientation: 中重新加载CollectionView 的数据。

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

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

                    【讨论】:

                      【解决方案20】:

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

                      - (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 的视图中,但对于我当前的项目来说,这是合乎逻辑的方式。

                      【讨论】:

                        【解决方案21】:

                        “即刻捕捉”的答案是正确的方法,不需要使用快照覆盖 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);
                        }
                        

                        【讨论】:

                          【解决方案22】:

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

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

                          我希望这会有所帮助。

                          【讨论】:

                            【解决方案23】:

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

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

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

                            【讨论】:

                              【解决方案24】:

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

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

                              【讨论】:

                                猜你喜欢
                                • 2017-05-29
                                • 1970-01-01
                                • 2014-03-19
                                • 2015-04-09
                                • 1970-01-01
                                • 2012-12-13
                                • 1970-01-01
                                • 2018-03-16
                                • 1970-01-01
                                相关资源
                                最近更新 更多