UICollectionView - 在设备旋转上调整单元格大小 - Swift

Posted

技术标签:

【中文标题】UICollectionView - 在设备旋转上调整单元格大小 - Swift【英文标题】:UICollectionView - resizing cells on device rotate - Swift 【发布时间】:2016-03-12 00:11:54 【问题描述】:

我创建了一个 UICollectionView,以便我可以将视图排列成整齐的列。我希望在 > 500 像素宽的设备上有一列。

为了实现这一点,我创建了这个函数:

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize 
    let size = collectionView.frame.width
    if (size > 500) 
        return CGSize(width: (size/2) - 8, height: (size/2) - 8)
    
    return CGSize(width: size, height: size)

这在第一次加载时按预期工作,但是当我旋转设备时,计算并不总是再次发生,并且视图并不总是按预期重绘。这是我的设备旋转时的代码:

override func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) 
    collectionView.collectionViewLayout.invalidateLayout()
    self.view.setNeedsDisplay()

我假设我忘记重绘某些东西,但我不确定是什么。任何想法都非常感谢!

【问题讨论】:

这可能会帮助某人***.com/questions/13490065/… 【参考方案1】:

您可以使用viewWillLayoutSubviews。 This question 应该会有所帮助,但这基本上是在视图控制器视图即将布局其子视图时调用的。

所以您的代码将如下所示:

override func viewWillLayoutSubviews() 
  super.viewWillLayoutSubviews()

  guard let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else 
    return
  

  if UIInterfaceOrientationIsLandscape(UIApplication.sharedApplication().statusBarOrientation) 
    //here you can do the logic for the cell size if phone is in landscape
   else 
    //logic if not landscape 
  

  flowLayout.invalidateLayout()

【讨论】:

使用这种方法会进入一个无限循环。 我不确定@valvoline 是否正确,这会导致无限循环 - 但是它很可能会导致性能问题,因为它使 collectionview 布局失效的次数远远超过了必要的次数。最好使用viewWillTransitionToSize 此代码导致无限循环。它不应该是正确的答案。 使用此方案时应用死循环 这应该被取消标记为正确并替换为viewWillTransitionToSize 方法。【参考方案2】:

我倾向于将viewWillTransitionToSize 用于相同的事情,并在其中简单地拨打invalidateLayout()

【讨论】:

【参考方案3】:

也许最直接的方法是在 viewWillTransitionToSize 期间使布局无效:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) 
    super.viewWillTransition(to: size, with: coordinator)
    guard let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else 
        return
    
    flowLayout.invalidateLayout()

【讨论】:

当你的viewcontroller是一个未折叠的splitviewcontroller的detailcontroller时,这个方法不会被调用。 这让我非常接近。【参考方案4】:

我有一个任务来调整 UICollectionView 子类中的项目大小。最好的方法是 setFrame。我从 ViewController 传递的属性 collectionViewFlowLayout(在我的情况下,它是默认流布局的出口)。

// .h
@property (nonatomic, weak) UICollectionViewFlowLayout *collectionViewFlowLayout;

// .m
- (void)setFrame:(CGRect)frame 
    if (!CGSizeEqualToSize(frame.size, self.frame.size)) 
        collectionViewFlowLayout.itemSize =
        CGSizeMake((UIDeviceOrientationIsLandscape(UIDevice.currentDevice.orientation) ? (frame.size.width - 10) / 2 : frame.size.width), collectionViewFlowLayout.itemSize.height);
    
    [super setFrame:frame];

【讨论】:

【参考方案5】:

也可以使用traitCollectionDidChange 方法代替viewWillLayoutSubviews

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) 
    super.traitCollectionDidChange(previousTraitCollection)

    guard let previousTraitCollection = previousTraitCollection, traitCollection.verticalSizeClass != previousTraitCollection.verticalSizeClass ||
        traitCollection.horizontalSizeClass != previousTraitCollection.horizontalSizeClass else 
            return
    

    if traitCollection.horizontalSizeClass == .regular && traitCollection.verticalSizeClass == .regular 
        // iPad portrait and landscape
        // do something here...
    
    if traitCollection.horizontalSizeClass == .compact && traitCollection.verticalSizeClass == .regular 
        // iPhone portrait
        // do something here...
    
    if traitCollection.horizontalSizeClass == .regular && traitCollection.verticalSizeClass == .compact 
        // iPhone landscape
        // do something here...
    
    collectionView?.collectionViewLayout.invalidateLayout()
    collectionView?.reloadData()

【讨论】:

【参考方案6】:

您可以创建一个遮罩内容并将其移动到collectionView。横向/纵向动画完成后,您必须尽快将其删除。

这是一个例子:

@property (strong, nonatomic) UIImageView *maskImage;

.........

- (UIImageView *) imageForCellAtIndex: (NSInteger) index 
    UICollectionView *collectionView = self.pagerView.test;
    FSPagerViewCell *cell = nil;
    NSArray *indexPaths = [collectionView indexPathsForVisibleItems];
    for (NSIndexPath *indexPath in indexPaths) 
        if (indexPath.item == index) 
            cell = (FSPagerViewCell *)[collectionView cellForItemAtIndexPath: indexPath];
            break;
        
    
    if (cell) 
        return cell.imageView;
    
    return nil;



- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator


    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];

    [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context)
     
         UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
         [self test_didRotateFromInterfaceOrientation: orientation];

         UIImageView *imageView = [self imageForCellAtIndex: self.pagerView.currentIndex];
         if (imageView) 
                 UIImageView *imageView = [self imageForCellAtIndex: self.pagerView.currentIndex];
                 CGSize itemSize = self.pagerView.itemSize;
                 UIImageView *newImage = [[UIImageView alloc] initWithImage: imageView.image];
                 [newImage setFrame: CGRectMake((_contentView.bounds.size.width - itemSize.width)/2.0f, 0, itemSize.width, itemSize.height)];
                 newImage.contentMode = imageView.contentMode;
                 newImage.clipsToBounds = imageView.clipsToBounds;
                 newImage.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
                 [self.pagerView addSubview: newImage];

                 self.maskImage = newImage;
         

         [self.pagerView.test performBatchUpdates:^
             [self.pagerView.test setCollectionViewLayout:self.pagerView.test.collectionViewLayout animated:YES];
          completion:nil];
         // do whatever
      completion:^(id<UIViewControllerTransitionCoordinatorContext> context)
     
         [self.maskImage removeFromSuperview];
     ];

【讨论】:

【参考方案7】:

我使用了以下方法,这对我有用。 我的问题是我在

中使布局无效

viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)

但此时(正如此方法名称所暗示的)设备尚未旋转。 所以这个方法会在viewWillLayoutSubviews之前被调用,因此在这个方法中我们没有右边界和框架(安全区域),因为设备会在之后旋转。

所以我使用了通知

override func viewDidAppear(_ animated: Bool) 
    super.viewDidAppear(animated)
    NotificationCenter.default.addObserver(self, selector: #selector(rotated), name: UIDevice.orientationDidChangeNotification, object: nil)


override func viewDidDisappear(_ animated: Bool) 
    super.viewDidDisappear(animated)
    NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)


@objc func rotated()
    guard let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else 
        return
    
    flowLayout.invalidateLayout()

然后在集合视图流委托方法中,一切都按预期工作。

extension ViewController: UICollectionViewDelegateFlowLayout
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
        if #available(ios 11.0, *) 
            return CGSize(width: view.safeAreaLayoutGuide.layoutFrame.width, height: 70)
         else 
            return CGSize(width: view.frame.width, height: 70)
        
    

【讨论】:

到目前为止,这是野兽派的做法。谢谢 叫我傻逼。但我多年来一直在编码,我只是喜欢可以在我的项目中拖放的代码。最好的方法!谢谢!【参考方案8】:

要让集合视图调整其单元格的大小,并在旋转期间使更改动画化,请使用过渡协调器:

(斯威夫特 4+)

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

    // Have the collection view re-layout its cells.
    coordinator.animate(
        alongsideTransition:  _ in self.collectionView.collectionViewLayout.invalidateLayout() ,
        completion:  _ in 
    )

【讨论】:

这是唯一让我在集合视图大小更改和单元格居中期间平滑过渡的东西【参考方案9】:

这适用于可能具有相同特殊情况的其他人。 情况: 我在表格视图控制器中包含了UICollectionView 作为UITableViewCell。行高设置为UITableViewAutomaticDimension 以适应集合视图中的单元格数量。尽管在同一表格视图中具有动态内容的其他单元格的行为正确,但它在设备旋转时布局不正确。经过长时间的研究,我找到了一个可行的解决方案:

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


- (void) reloadTableViewSilently 
    dispatch_async(dispatch_get_main_queue(), ^
// optional: [UIView setAnimationsEnabled:false];
        [self.tableView beginUpdates];
        [self.tableView endUpdates];
// optional: [UIView setAnimationsEnabled:true];
    );

【讨论】:

以上是关于UICollectionView - 在设备旋转上调整单元格大小 - Swift的主要内容,如果未能解决你的问题,请参考以下文章

设备旋转时如何自动调整 UICollectionView 的大小?

Swift:如何在设备旋转后刷新UICollectionView布局

UICollectionView 在旋转时重新排列单元格

如何在自定义 UICollectionView 的旋转后触发 drawrect?

屏幕旋转后如何在 UICollectionView 中跟踪单元格的移动

旋转后 UICollectionView 单元格不水平