【问题标题】:UICollectionView - dynamic cell height? [duplicate]UICollectionView - 动态单元格高度? [复制]
【发布时间】:2015-03-25 13:23:07
【问题描述】:

我需要显示一堆具有不同高度的 collectionViewCell。视图太复杂了,我不想手动计算预期的高度。我想强制执行自动布局来计算单元格高度

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

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

有什么解决办法吗?

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,就会发生崩溃。如果我不调用该方法而是返回一个大小,一切都会很好,并且单元格会在没有计算出的大小的情况下显示

流布局不支持负数或零大小

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

【问题讨论】:

标签: ios swift uicollectionview


【解决方案1】:

这是一个Ray Wenderlich tutorial,它向您展示了如何使用 AutoLayout 来动态调整UITableViewCells 的大小。我认为UICollectionViewCell 也是如此。

不过,基本上,您最终会出列并配置原型单元格并获取其高度。读完这篇文章后,我决定实现这个方法,只写一些清晰、明确的大小调整代码。

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

- (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)给了我这个:

【讨论】:

  • 这不起作用。在 collectionView 上它崩溃了。
  • ...在哪里崩溃,什么样的崩溃?
  • 感谢您的回答,这太糟糕了,这是不可能的
  • 嗯,这不可能是您想要的方式(我同意,这是一个合理的期望),但仍然可能使用 AutoLayout 来动态调整细胞。所以,至少赢了一半。
  • 这是一个很好的答案。你愿意在 GitHub 上发布这个演示项目吗?
【解决方案2】:

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

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

  2. 在该 .xib 中添加允许从上到下计算单元格的约束。如果您没有考虑所有高度,则重新调整大小将不起作用。假设您在顶部有一个视图,然后在其下方有一个标签,在其下方有另一个标签。您需要将约束连接到单元格的顶部到该视图的顶部,然后将视图的底部连接到第一个标签的顶部,第一个标签的底部到第二个标签的顶部,以及第二个标签的底部到单元格底部。

  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 中与该单元格关联的数据源中提取数据。然后配置 sizingCell 并调用preferredLayoutSizeFittingSize。该方法返回一个 CGSize,它将由宽度减去内容插入和从 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 应该是多少。

    func 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 或其他时髦的数字,那么您没有正确设置约束。

【讨论】:

  • 这是一段很棒的代码。真的帮了我大忙。我有一个带有 stackpanel 和两个扩展标签的单元格 - 现在单元格会扩展以适应内容!
  • @JamesMundy 感谢您的评论,我希望它能帮助某人清除它!
  • @bolnad:宾果游戏!有效。但我想创建一个自动扩展单元格,根据它在选择时扩展/收缩..有什么想法吗?
  • @AbhishekBedi 不确定您的要求,听起来您想在点击另一个单元格时创建一个插入单元格?苹果在日历应用程序中的内联选择器是怎样的?
  • 为什么 iOS 如此……痛苦?在 android 的 ListView(或其他回收器)中,我们只返回视图,仅此而已。大小必须由视图决定,而不是由集合的视图控制器决定。
【解决方案3】:

我们可以在没有 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。

【讨论】:

  • 希望我能投票两次!
  • 如此简单的解决方案!我在我的集​​合视图单元格中创建了一个静态函数来执行此计算并返回高度。
【解决方案4】:

Swift 4.*

我为 UICollectionViewCell 创建了一个 Xib,这似乎是个好方法。

extension ViewController: UICollectionViewDelegateFlowLayout {

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return size(indexPath: indexPath)
    }

    private func size(for indexPath: IndexPath) -> CGSize {
        // load cell from Xib
        let cell = Bundle.main.loadNibNamed("ACollectionViewCell", owner: self, options: nil)?.first as! ACollectionViewCell

        // configure cell with data in it
        let data = self.data[indexPath.item]
        cell.configure(withData: data)

        cell.setNeedsLayout()
        cell.layoutIfNeeded()

        // width that you want
        let width = collectionView.frame.width
        let height: CGFloat = 0

        let targetSize = CGSize(width: width, height: height)

        // get size with width that you want and automatic height
        let size = cell.contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .defaultHigh, verticalFittingPriority: .fittingSizeLevel)
        // if you want height and width both to be dynamic use below
        // let size = cell.contentView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)

        return size
    }
}

#note:我不建议在这种大小确定的情况下配置数据时设置图像。它给了我扭曲/不想要的结果。配置文本只给了我以下结果。

【讨论】:

    【解决方案5】:

    TL;DR:向下扫描到图像,然后查看working project here.

    更新我的答案以获得我找到的更简单的解决方案..

    就我而言,我想固定宽度,并拥有可变高度的单元格。我想要一个可重复使用的解决方案,可以处理轮换并且不需要太多干预。

    我到达的结果是在集合单元格中覆盖(只是)systemLayoutFitting(...)(在这种情况下是我的基类),并首先通过添加约束来击败 UICollectionView 在contentView 上设置错误维度的努力已知尺寸,在本例中为宽度。

    class EstimatedWidthCell: UICollectionViewCell {
        override init(frame: CGRect) {
            super.init(frame: frame)
            contentView.translatesAutoresizingMaskIntoConstraints = false
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            contentView.translatesAutoresizingMaskIntoConstraints = false
        }
    
        override func systemLayoutSizeFitting(
            _ targetSize: CGSize, withHorizontalFittingPriority
            horizontalFittingPriority: UILayoutPriority,
            verticalFittingPriority: UILayoutPriority) -> CGSize {
    
            width.constant = targetSize.width
    

    然后返回单元格的最终大小 - 用于(这感觉像一个错误)单元格本身的尺寸,但不是 contentView - 否则会被限制为冲突的大小(因此上面的约束) .为了计算正确的单元格大小,我对想要浮动的维度使用了较低的优先级,然后我得到了使内容适合我想要修复的宽度所需的高度:

            let size = contentView.systemLayoutSizeFitting(
                CGSize(width: targetSize.width, height: 1),
                withHorizontalFittingPriority: .required,
                verticalFittingPriority: verticalFittingPriority)
    
            print("\(#function) \(#line) \(targetSize) -> \(size)")
            return size
        }
    
        lazy var width: NSLayoutConstraint = {
            return contentView.widthAnchor
                .constraint(equalToConstant: bounds.size.width)
                .isActive(true)
        }()
    }
    

    但是这个宽度是从哪里来的呢?它是通过集合视图的流布局上的estimatedItemSize 配置的:

    lazy var collectionView: UICollectionView = {
        let view = UICollectionView(frame: CGRect(), collectionViewLayout: layout)
        view.backgroundColor = .cyan
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    lazy var layout: UICollectionViewFlowLayout = {
        let layout = UICollectionViewFlowLayout()
        let width = view.bounds.size.width // should adjust for inset
        layout.estimatedItemSize = CGSize(width: width, height: 10)
        layout.scrollDirection = .vertical
        return layout
    }()
    

    最后,为了处理旋转,我实现了trailCollectionDidChange 使布局无效:

    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        layout.estimatedItemSize = CGSize(width: view.bounds.size.width, height: 10)
        layout.invalidateLayout()
        super.traitCollectionDidChange(previousTraitCollection)
    }
    

    最终的结果是这样的:

    我已经发布了a working sample here.

    【讨论】:

    • 谢谢,我现在可以亲你了
    • 这似乎是最好的答案,因为它不需要为 Collection View Cell 使用外部 Nib (.xib)。所以这个解决方案是最灵活的。此外,它被抽象为 UICollectionViewCell 超类,可以被继承和重用。所以可以制作这样的AutomaticWidthCell、AutomaticHeightCell、AutomaticSizeCell,并且只在CollectionView Layout上设置estimatedItemSize。我在 UICollectionViewCell 中将它与 StackViews 一起使用,因此隐藏一些元素很容易折叠单元布局
    • 不错的解决方案,但我只想说你不应该在trailCollectionDidChange 中处理旋转,它会被要求更改任何特征,并且你可能会在不需要时使布局无效。 viewWillTransition 是正确的地方。
    【解决方案6】:

    似乎这是一个很受欢迎的问题,所以我会尽力做出我的微薄贡献。


    下面的代码是 Swift 4 无故事板设置的解决方案。它利用了以前答案中的一些方法,因此它可以防止在设备旋转时引起自动布局警告。

    如果代码示例有点长,我很抱歉。我想提供一个完全由 StackOverflow 托管的“易于使用”的解决方案。如果您对帖子有任何建议 - 请分享这个想法,我会相应地更新它。

    设置:

    两个类:ViewController.swiftMultilineLabelCell.swift - 包含单个 UILabel 的单元格。

    MultilineLabelCell.swift

    import UIKit
    
    class MultilineLabelCell: UICollectionViewCell {
        static let reuseId = "MultilineLabelCellReuseId"
    
        private let label: UILabel = UILabel(frame: .zero)
    
        override init(frame: CGRect) {
            super.init(frame: frame)
    
            layer.borderColor = UIColor.red.cgColor
            layer.borderWidth = 1.0
    
            label.numberOfLines = 0
            label.lineBreakMode = .byWordWrapping
    
            let labelInset = UIEdgeInsets(top: 10, left: 10, bottom: -10, right: -10)
            contentView.addSubview(label)
            label.translatesAutoresizingMaskIntoConstraints = false
            label.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor, constant: labelInset.top).isActive = true
            label.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor, constant: labelInset.left).isActive = true
            label.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor, constant: labelInset.right).isActive = true
            label.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor, constant: labelInset.bottom).isActive = true
    
            label.layer.borderColor = UIColor.black.cgColor
            label.layer.borderWidth = 1.0
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("Storyboards are quicker, easier, more seductive. Not stronger then Code.")
        }
    
        func configure(text: String?) {
            label.text = text
        }
    
        override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
            label.preferredMaxLayoutWidth = layoutAttributes.size.width - contentView.layoutMargins.left - contentView.layoutMargins.left
            layoutAttributes.bounds.size.height = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
            return layoutAttributes
        }
    }
    

    ViewController.swift

    import UIKit
    
    let samuelQuotes = [
        "Samuel says", 
        "Add different length strings here for better testing"
    ]
    
    class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
        private(set) var collectionView: UICollectionView
    
        // Initializers
        init() {
            // Create new `UICollectionView` and set `UICollectionViewFlowLayout` as its layout
            collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
            super.init(nibName: nil, bundle: nil)
        }
    
        required init?(coder aDecoder: NSCoder) {
            // Create new `UICollectionView` and set `UICollectionViewFlowLayout` as its layout
            collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
            super.init(coder: aDecoder)
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            title = "Dynamic size sample"
    
            // Register Cells
            collectionView.register(MultilineLabelCell.self, forCellWithReuseIdentifier: MultilineLabelCell.reuseId)
    
            // Add `coolectionView` to display hierarchy and setup its appearance
            view.addSubview(collectionView)
            collectionView.backgroundColor = .white
            collectionView.contentInsetAdjustmentBehavior = .always
            collectionView.contentInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
    
            // Setup Autolayout constraints
            collectionView.translatesAutoresizingMaskIntoConstraints = false
            collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
            collectionView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
            collectionView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
            collectionView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
    
            // Setup `dataSource` and `delegate`
            collectionView.dataSource = self
            collectionView.delegate = self
    
            (collectionView.collectionViewLayout as! UICollectionViewFlowLayout).estimatedItemSize = UICollectionViewFlowLayout.automaticSize
            (collectionView.collectionViewLayout as! UICollectionViewFlowLayout).sectionInsetReference = .fromLayoutMargins
        }
    
        // MARK: - UICollectionViewDataSource -
        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MultilineLabelCell.reuseId, for: indexPath) as! MultilineLabelCell
            cell.configure(text: samuelQuotes[indexPath.row])
            return cell
        }
    
        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            return samuelQuotes.count
        }
    
        // MARK: - UICollectionViewDelegateFlowLayout -
        func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
            let sectionInset = (collectionViewLayout as! UICollectionViewFlowLayout).sectionInset
            let referenceHeight: CGFloat = 100 // Approximate height of your cell
            let referenceWidth = collectionView.safeAreaLayoutGuide.layoutFrame.width
                - sectionInset.left
                - sectionInset.right
                - collectionView.contentInset.left
                - collectionView.contentInset.right
            return CGSize(width: referenceWidth, height: referenceHeight)
        }
    }
    

    要运行此示例,请创建新的 Xcode 项目,创建相应的文件并将 AppDelegate 内容替换为以下代码:

    import UIKit
    
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
    
        var window: UIWindow?
        var navigationController: UINavigationController?
    
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            window = UIWindow(frame: UIScreen.main.bounds)
    
            if let window = window {
                let vc = ViewController()
                navigationController = UINavigationController(rootViewController: vc)
                window.rootViewController = navigationController
                window.makeKeyAndVisible()
            }
    
            return true
        }
    }
    

    【讨论】:

      【解决方案7】:

      Swift 4 答案基于来自 @mbm29414 的有用答案。

      不幸的是,它需要使用 XIB 文件。似乎没有其他选择。

      关键部分是使用大小调整单元(仅创建一次)并在初始化集合视图时注册 XIB。

      然后在sizeForItemAt 函数中动态调整每个单元格的大小。

      // UICollectionView Vars and Constants
      let CellXIBName = YouViewCell.XIBName
      let CellReuseID = YouViewCell.ReuseID
      var sizingCell = YouViewCell()
      
      
      fileprivate func initCollectionView() {
          // Connect to view controller
          collectionView.dataSource = self
          collectionView.delegate = self
      
          // Register XIB
          collectionView.register(UINib(nibName: CellXIBName, bundle: nil), forCellWithReuseIdentifier: CellReuseID)
      
          // Create sizing cell for dynamically sizing cells
          sizingCell = Bundle.main.loadNibNamed(CellXIBName, owner: self, options: nil)?.first as! YourViewCell
      
          // Set scroll direction
          let layout = UICollectionViewFlowLayout()
          layout.scrollDirection = .vertical
          collectionView.collectionViewLayout = layout
      
          // Set properties
          collectionView.alwaysBounceVertical = true
          collectionView.alwaysBounceHorizontal = false
      
          // Set top/bottom padding
          collectionView.contentInset = UIEdgeInsets(top: collectionViewTopPadding, left: collectionViewSidePadding, bottom: collectionViewBottomPadding, right: collectionViewSidePadding)
      
          // Hide scrollers
          collectionView.showsVerticalScrollIndicator = false
          collectionView.showsHorizontalScrollIndicator = false
      }
      
      
      func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
          // Get cell data and render post
          let data = YourData[indexPath.row]
          sizingCell.renderCell(data: data)
      
          // Get cell size
          sizingCell.setNeedsLayout()
          sizingCell.layoutIfNeeded()
          let cellSize = sizingCell.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
      
          // Return cell size
          return cellSize
      }
      

      【讨论】:

      • 让我们看看我是否做对了。 YouViewCell 不是您实际的单元格类或实际的单元格 Xib?它是专门为处理大小而创建的?或者......我是否必须将我的实际单元格设计从我的情节提要中取出并将它们放在单独的 Xib 中?
      • 这对我有用。谢谢
      【解决方案8】:

      我按照此 SO 中提到的步骤进行操作,一切都很好,除非我的 Collection View 的数据(文本)较少以使其足够宽。检查systemLyaoutSizeFittingSize 中的文档,我有这个解决方案,所以我的单元格占用了我要求的宽度:

      - (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: with UILayoutPriorityFittingSizeLevel 两个优先级。

      虽然根据 Apple 的文档,默认值为“相当低”:

      当您发送 -[UIView systemLayoutSizeFittingSize:] 时,将计算最接近目标尺寸(参数)的尺寸。 UILayoutPriorityFittingSizeLevel 是视图希望在计算中符合目标大小的优先级。这是相当低的。在这个优先级上进行约束通常是不合适的。你想要更高或更低。

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

      【讨论】:

        【解决方案9】:

        按照 bolnad answer 直到第 4 步。

        然后将所有其他步骤替换为:

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

        【讨论】:

          【解决方案10】:

          它对我有用,希望你也一样。

          *注意:我在 Nib 中使用过自动布局,记得在 contentView 中为子视图添加顶部和底部约束

          func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
                     let cell = YourCollectionViewCell.instantiateFromNib()
                     cell.frame.size.width = collectionView.frame.width
                     cell.data = viewModel.data[indexPath.item]
                     let resizing = cell.systemLayoutSizeFitting(UILayoutFittingCompressedSize, withHorizontalFittingPriority: UILayoutPriority.required, verticalFittingPriority: UILayoutPriority.fittingSizeLevel)
                     return resizing
              }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2020-12-27
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多