UICollectionView – dynamic细胞高度?

我需要显示一堆具有不同高度的collectionViewCells。 意见太复杂,我不想手动计算预期的高度。 我想执行自动布局来计算单元格高度

调用cellForItemAtIndexPath之外的cellForItemAtIndexPath会破坏collectionView并导致崩溃

另一个问题是单元格不在单独的xib中,所以我不能手动实例化一个临时的单元格并将其用于高度计算。

任何解决scheme?

 public func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize { var cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellIdentifier, forIndexPath: indexPath) as UICollectionViewCell configureCell(cell, item: items[indexPath.row]) cell.contentView.setNeedsLayout() cell.contentView.layoutIfNeeded() return cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) } 

编辑:

一旦调用dequeueReusableCellWithReuseIdentifier,就会发生崩溃。 如果我不调用这种方法,而是返回一个大小,一切都很好,单元格显示没有计算的大小

stream布局中不支持负值或零大小

 2015-01-26 18:24:34.231 [13383:9752256] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 1 beyond bounds [0 .. 0]' *** First throw call stack: ( 0 CoreFoundation 0x00000001095aef35 __exceptionPreprocess + 165 1 libobjc.A.dylib 0x0000000109243bb7 objc_exception_throw + 45 2 CoreFoundation 0x0000000109499f33 -[__NSArrayM objectAtIndex:] + 227 3 UIKit 0x0000000107419d9c -[UICollectionViewFlowLayout _getSizingInfos] + 842 4 UIKit 0x000000010741aca9 -[UICollectionViewFlowLayout _fetchItemsInfoForRect:] + 526 5 UIKit 0x000000010741651f -[UICollectionViewFlowLayout prepareLayout] + 257 6 UIKit 0x000000010742da10 -[UICollectionViewData _prepareToLoadData] + 67 7 UIKit 0x00000001074301c6 -[UICollectionViewData layoutAttributesForItemAtIndexPath:] + 44 8 UIKit 0x00000001073fddb1 -[UICollectionView _dequeueReusableViewOfKind:withIdentifier:forIndexPath:viewCategory:] + 248 9 0x00000001042b824c _TFC1228BasePaginatingViewController14collectionViewfS0_FTCSo16UICollectionView6layoutCSo22UICollectionViewLayout22sizeForItemAtIndexPathCSo11NSIndexPath_VSC6CGSize + 700 10 0x00000001042b83d4 _TToFC1228BasePaginatingViewController14collectionViewfS0_FTCSo16UICollectionView6layoutCSo22UICollectionViewLayout22sizeForItemAtIndexPathCSo11NSIndexPath_VSC6CGSize + 100 11 UIKit 0x0000000107419e2e -[UICollectionViewFlowLayout _getSizingInfos] + 988 12 UIKit 0x000000010741aca9 -[UICollectionViewFlowLayout _fetchItemsInfoForRect:] + 526 13 UIKit 0x000000010741651f -[UICollectionViewFlowLayout prepareLayout] + 257 14 UIKit 0x000000010742da10 -[UICollectionViewData _prepareToLoadData] + 67 15 UIKit 0x000000010742e0e9 -[UICollectionViewData validateLayoutInRect:] + 54 16 UIKit 0x00000001073f67b8 -[UICollectionView layoutSubviews] + 170 17 UIKit 0x0000000106e3c973 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 521 18 QuartzCore 0x0000000106b0fde8 -[CALayer layoutSublayers] + 150 19 QuartzCore 0x0000000106b04a0e _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 380 20 QuartzCore 0x0000000106b0487e _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 24 21 QuartzCore 0x0000000106a7263e _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 242 22 QuartzCore 0x0000000106a7374a _ZN2CA11Transaction6commitEv + 390 23 QuartzCore 0x0000000106a73db5 _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 89 24 CoreFoundation 0x00000001094e3dc7 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23 25 CoreFoundation 0x00000001094e3d20 __CFRunLoopDoObservers + 368 26 CoreFoundation 0x00000001094d9b53 __CFRunLoopRun + 1123 27 CoreFoundation 0x00000001094d9486 CFRunLoopRunSpecific + 470 28 GraphicsServices 0x000000010be869f0 GSEventRunModal + 161 29 UIKit 0x0000000106dc3420 UIApplicationMain + 1282 30 0x000000010435c709 main + 169 31 libdyld.dylib 0x000000010a0f2145 start + 1 ) libc++abi.dylib: terminating with uncaught exception of type NSException 

这是一个Ray Wenderlich教程 ,向您展示如何使用AutoLayoutdynamic调整UITableViewCell的大小。 我想这将是相同的UICollectionViewCell

但是,基本上,你最终需要去队列和configuration一个原型单元并抓取它的高度。 读完这篇文章之后,我决定实施这个方法,只写一些清晰明确的大小代码。

以下是我认为整篇文章的“秘诀”:

 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return [self heightForBasicCellAtIndexPath:indexPath]; } - (CGFloat)heightForBasicCellAtIndexPath:(NSIndexPath *)indexPath { static RWBasicCell *sizingCell = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sizingCell = [self.tableView dequeueReusableCellWithIdentifier:RWBasicCellIdentifier]; }); [self configureBasicCell:sizingCell atIndexPath:indexPath]; return [self calculateHeightForConfiguredSizingCell:sizingCell]; } - (CGFloat)calculateHeightForConfiguredSizingCell:(UITableViewCell *)sizingCell { [sizingCell setNeedsLayout]; [sizingCell layoutIfNeeded]; CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]; return size.height + 1.0f; // Add 1.0f for the cell separator height } 

编辑:我做了一些研究到你的崩溃,并决定没有办法得到这个没有自定义的XIB完成。 虽然这有点令人沮丧,但是您应该能够从Storyboard剪切并粘贴到自定义的空XIB。

一旦你完成了,像下面这样的代码将会让你继续:

 // ViewController.m #import "ViewController.h" #import "CollectionViewCell.h" @interface ViewController () <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout> { } @property (weak, nonatomic) IBOutlet CollectionViewCell *cell; @property (weak, nonatomic) IBOutlet UICollectionView *collectionView; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor lightGrayColor]; [self.collectionView registerNib:[UINib nibWithNibName:@"CollectionViewCell" bundle:nil] forCellWithReuseIdentifier:@"cell"]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; NSLog(@"viewDidAppear..."); } - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { return 1; } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return 50; } - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section { return 10.0f; } - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { return 10.0f; } - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { return [self sizingForRowAtIndexPath:indexPath]; } - (CGSize)sizingForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *title = @"This is a long title that will cause some wrapping to occur. This is a long title that will cause some wrapping to occur."; static NSString *subtitle = @"This is a long subtitle that will cause some wrapping to occur. This is a long subtitle that will cause some wrapping to occur."; static NSString *buttonTitle = @"This is a really long button title that will cause some wrapping to occur."; static CollectionViewCell *sizingCell = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sizingCell = [[NSBundle mainBundle] loadNibNamed:@"CollectionViewCell" owner:self options:nil][0]; }); [sizingCell configureWithTitle:title subtitle:[NSString stringWithFormat:@"%@: Number %d.", subtitle, (int)indexPath.row] buttonTitle:buttonTitle]; [sizingCell setNeedsLayout]; [sizingCell layoutIfNeeded]; CGSize cellSize = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]; NSLog(@"cellSize: %@", NSStringFromCGSize(cellSize)); return cellSize; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { static NSString *title = @"This is a long title that will cause some wrapping to occur. This is a long title that will cause some wrapping to occur."; static NSString *subtitle = @"This is a long subtitle that will cause some wrapping to occur. This is a long subtitle that will cause some wrapping to occur."; static NSString *buttonTitle = @"This is a really long button title that will cause some wrapping to occur."; CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath]; [cell configureWithTitle:title subtitle:[NSString stringWithFormat:@"%@: Number %d.", subtitle, (int)indexPath.row] buttonTitle:buttonTitle]; return cell; } @end 

上面的代码(以及一个非常基本的UICollectionViewCell子类和关联的XIB)给了我这个:

在这里输入图像描述

我刚刚在UICollectionView中遇到了这个问题,并且以类似于上面的答案的方式解决了这个问题,但是采用了纯粹的UICollectionView方式。

  1. 创build一个自定义的UICollectionViewCell,其中包含您将使用dynamic填充的任何内容。 我为它创build了自己的.xib,因为它似乎是最简单的方法。

  2. 在.xib中添加允许从上到下计算单元格的约束。 如果你没有考虑到所有的高度,重新resize将不起作用。 假设你有一个顶部的视图,然后是下面的标签,下面有一个标签。 您需要将约束条件连接到该视图的顶部,然后将视图的底部连接到第一个标签的顶部,将第一个标签的底部连接到第二个标签的顶部,将第二个标签的底部连接到底部到细胞的底部。

  3. 将.xib加载到viewcontroller中,并使用viewDidLoad上的collectionView进行注册

     let nib = UINib(nibName: CustomCellName, bundle: nil) self.collectionView!.registerNib(nib, forCellWithReuseIdentifier: "customCellID")` 
  4. 将该xib的第二个副本加载到类中,并将其作为属性存储,以便可以使用它来确定该单元格的大小

     let sizingNibNew = NSBundle.mainBundle().loadNibNamed(CustomCellName, owner: CustomCellName.self, options: nil) as NSArray self.sizingNibNew = (sizingNibNew.objectAtIndex(0) as? CustomViewCell)! 
  5. 在视图控制器中实现UICollectionViewFlowLayoutDelegate 。 重要的方法称为sizeForItemAtIndexPath 。 在该方法中,您将需要从与indexPath关联的单元格的数据源中提取数据。 然后configurationsizingCell并调用preferredLayoutSizeFittingSize 。 该方法返回一个CGSize,它由宽度减去内容插入和从self.sizingCell.preferredLayoutSizeFittingSize(targetSize)返回的高度self.sizingCell.preferredLayoutSizeFittingSize(targetSize)

     override func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize { guard let data = datasourceArray?[indexPath.item] else { return CGSizeZero } let sectionInset = self.collectionView?.collectionViewLayout.sectionInset let widthToSubtract = sectionInset!.left + sectionInset!.right let requiredWidth = collectionView.bounds.size.width let targetSize = CGSize(width: requiredWidth, height: 0) sizingNibNew.configureCell(data as! CustomCellData, delegate: self) let adequateSize = self.sizingNibNew.preferredLayoutSizeFittingSize(targetSize) return CGSize(width: (self.collectionView?.bounds.width)! - widthToSubtract, height: adequateSize.height) } 
  6. 在自定义单元本身的类,你将需要重写awakeFromNib并告诉contentView ,其大小需要灵活

      override func awakeFromNib() { super.awakeFromNib() self.contentView.autoresizingMask = [UIViewAutoresizing.FlexibleHeight] } 
  7. 在自定义单元格覆盖layoutSubviews

      override func layoutSubviews() { self.layoutIfNeeded() } 
  8. 在自定义单元类的preferredLayoutSizeFittingSize类中。 这是你需要做的任何欺骗项目正在布局。 如果它的标签,你将需要告诉它什么它的preferredMaxWidth应该是。

     preferredLayoutSizeFittingSize(targetSize: CGSize)-> CGSize { let originalFrame = self.frame let originalPreferredMaxLayoutWidth = self.label.preferredMaxLayoutWidth var frame = self.frame frame.size = targetSize self.frame = frame self.setNeedsLayout() self.layoutIfNeeded() self.label.preferredMaxLayoutWidth = self.questionLabel.bounds.size.width // calling this tells the cell to figure out a size for it based on the current items set let computedSize = self.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) let newSize = CGSize(width:targetSize.width, height:computedSize.height) self.frame = originalFrame self.questionLabel.preferredMaxLayoutWidth = originalPreferredMaxLayoutWidth return newSize } 

所有这些步骤应该给你正确的大小。 如果你得到0或其他时髦的数字比你没有正确设置你的约束。

我们可以保持集合视图单元格的dynamic高度没有XIB(只使用故事板)。

 - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { NSAttributedString* labelString = [[NSAttributedString alloc] initWithString:@"Your long string goes here" attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:17.0]}]; CGRect cellRect = [labelString boundingRectWithSize:CGSizeMake(cellWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin context:nil]; return CGSizeMake(cellWidth, cellRect.size.height); } 

确保IB中的numberOfLines应该是0。

我遵循这个SO中提到的步骤,一切都很好,除非我的集合视图具有较less的数据(文本),使其足够宽。 检查systemLyaoutSizeFittingSize的文档,我有这个解决scheme,所以我的单元格占据了我所要求的宽度:

 - (CGSize)calculateSizeForSizingCell:(UICollectionViewCell *)sizingCell width:(CGFloat)width { CGRect frame = sizingCell.frame; frame.size.width = width; sizingCell.frame = frame; [sizingCell setNeedsLayout]; [sizingCell layoutIfNeeded]; CGSize size = [sizingCell systemLayoutSizeFittingSize:UILayoutFittingCompressedSize withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel]; return size; } 

希望这将有助于某人。

 - (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSize NS_AVAILABLE_IOS(6_0); 

苹果文档:

等同于发送-systemLayoutSizeFittingSize:withHorizo​​ntalFittingPriority:verticalFittingPriority:对于两个优先级,使用UILayoutPriorityFittingSizeLevel。

根据苹果公司的文档,默认值是“非常低的”

当你发送 – [UIView systemLayoutSizeFittingSize:],计算出最接近目标大小(参数)的尺寸。 UILayoutPriorityFittingSizeLevel是视图想要符合该计算中的目标大小的优先级。 这是相当低的。 确切地说,在这个优先权上制约是不合适的。 你想要更高或更低。

所以我的默认行为的改变是强制使用UILayoutPriorityRequired的宽度(水平拟合)。

按照bolnad的答案直到第4步。

然后通过replace所有其他步骤来简化:

 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { // Configure your cell sizingNibNew.configureCell(data as! CustomCellData, delegate: self) // We use the full width minus insets let width = collectionView.frame.size.width - collectionView.sectionInset.left - collectionView.sectionInset.right // Constrain our cell to this width let height = sizingNibNew.systemLayoutSizeFitting(CGSize(width: width, height: .infinity), withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityFittingSizeLevel).height return CGSize(width: width, height: height) }