UICollectionView,全宽单元格,允许自动布局dynamic高度?

这是关于当前iOS开发的一个相当基本的问题,我从来没有find一个好的答案。


在(说)垂直的UICollectionView

是否可以有全angular单元格 ,但是允许dynamic高度由自动布局控制?

(如果你是iOS新手“dynamic高度”,这意味着单元格有一些文本视图,可以是任何长度或图像可能是不同的大小,所以最终每个单元格是完全不同的高度。

如果这样怎么样?

这也许是“iOS中最重要的问题,没有很好的答案”。

问题

你正在寻找自动高度,也希望有宽度,这是不可能得到使用UICollectionViewFlowLayoutAutomaticSize 。 为什么你不采取UITableVIew而不是UICollectionView ,你可以在UITableViewAutomaticDimension返回heightForRow ,它将被自动pipe理。 无论如何,你想使用UICollectionView如下所示是你的解决scheme。

步骤I:计算Cell的预期高度

1 UILable如果你在ColllectionViewCell只有UILable比设置numberOfLines=0并且计算了UIlable的期望高度, UIlable通过这三个参数

  func heightForLable(text:String, font:UIFont, width:CGFloat) -> CGFloat{ // pass string ,font , LableWidth let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude)) label.numberOfLines = 0 label.lineBreakMode = NSLineBreakMode.byWordWrapping label.font = font label.text = text label.sizeToFit() return label.frame.height } 

2.如果你的ColllectionViewCell只包含UIImageView ,并且它的高度应该是dynamic的,那么你需要获得UIImage的高度(你的UIImageView必须有AspectRatio约束)

 // this will give you the height of your Image let heightInPoints = image.size.height let heightInPixels = heightInPoints * image.scale 

3.如果它既包含计算它们的高度,又将它们加在一起。

STEP-II :返回CollectionViewCell的大小

1.在viewController中添加UICollectionViewDelegateFlowLayout委托

2.实现委托方法

 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { // This is Just for example , for the scenario Step-I -> 1 let yourWidthOfLable=self.view.size.width let font = UIFont(name: "Helvetica", size: 20.0) var expectedHeight = heightForLable(array[indePath.row], font: font, width:yourWidthOfLable ) return CGSize(width: view.frame.width, height: expectedHeight) } 

我希望这会帮助你。

就我个人而言,我发现有一个最好的方法来使用UICollectionView其中AutoLayout确定大小,而每个单元格可以有不同的大小是实现UICollectionViewDelegateFlowLayout sizeForItemAtIndexPath函数,同时使用实际单元格来衡量的大小。

我在我的一篇博文中提到过这个问题: https : //janthielemann.de/ios-development/self-sizing-uicollectionviewcells-ios-10-swift-3/

希望这个会帮助你实现你想要的。 我不是100%确定,但我相信不像UITableView,你可以实际上有一个完全自动高度的单元格使用AutoLayout结合

 tableView.rowHeight = UITableViewAutomaticDimension tableView.estimatedRowHeight = 44 

UICollectionView没有让AutoLayout确定尺寸的方法,因为UICollectionViewCell不一定会填充整个屏幕的宽度。

但是,对于你来说,这是一个问题 :如果你需要全屏宽度的单元格,为什么还要使用UICollectionView而不是自动resize的单元格?

有几种方法可以解决这个问题。

一种方法是可以给集合视图stream布局估计大小并计算单元大小。

注意:正如在下面的评论中提到的,从iOS 10开始,您不再需要提供和估计大小来触发对cell func preferredLayoutAttributesFitting(_ layoutAttributes:) 的调用 以前(iOS 9)会要求您提供一个估计的大小,如果你想查询一个单元prefferedLayoutAttributes。

(假设您正在使用故事板,并通过IB连接collections视图)

 override func viewDidLoad() { super.viewDidLoad() let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout layout?.estimatedItemSize = CGSize(width: 375, height: 200) // your average cell size } 

对于简单的细胞通常是足够的。 如果大小仍然不正确,在集合视图单元格中,您可以重写func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes ,这将为您提供对单元格大小的更好的纹理控制。 注意:您仍然需要给stream布局估计大小

然后重写func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes返回正确的大小。

 override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { let autoLayoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes) let targetSize = CGSize(width: layoutAttributes.frame.width, height: 0) let autoLayoutSize = contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityDefaultLow) let autoLayoutFrame = CGRect(origin: autoLayoutAttributes.frame.origin, size: autoLayoutSize) autoLayoutAttributes.frame = autoLayoutFrame return autoLayoutAttributes } 

或者,也可以使用大小单元格来计算UICollectionViewDelegateFlowLayout单元格的UICollectionViewDelegateFlowLayout

 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let width = collectionView.frame.width let size = CGSize(width: width, height: 0) // assuming your collection view cell is a nib // you may also instantiate a instance of our cell from a storyboard let sizingCell = UINib(nibName: "yourNibName", bundle: nil).instantiate(withOwner: nil, options: nil).first as! YourCollectionViewCell sizingCell.autoresizingMask = [.flexibleWidth, .flexibleHeight] sizingCell.frame.size = size sizingCell.configure(with: object[indexPath.row]) // what ever method configures your cell return sizingCell.contentView.systemLayoutSizeFitting(size, withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityDefaultLow) } 

虽然这些都不是完美的生产准备的例子,他们应该让你开始正确的方向。 我不能说这是最好的做法,但是对我来说,即使是包含多个标签的相当复杂的单元格,也可能不包装多行。

不知道这是否是一个“非常好的答案”,但这是我用来完成这个。 我的stream布局是水平的,我试图使宽度调整与自动布局,所以它是类似于你的情况。

 extension PhotoAlbumVC: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { // My height is static, but it could use the screen size if you wanted return CGSize(width: collectionView.frame.width - sectionInsets.left - sectionInsets.right, height: 60) } } 

然后在自动布局约束被修改的视图控制器中,我触发了一个NSNotification。

 NotificationCenter.default.post(name: NSNotification.Name("constraintMoved"), object: self, userInfo: nil) 

在我的UICollectionView子类中,我听那个通知:

 // viewDidLoad NotificationCenter.default.addObserver(self, selector: #selector(handleConstraintNotification(notification:)), name: NSNotification.Name("constraintMoved"), object: nil) 

并使布局无效:

 func handleConstraintNotification(notification: Notification) { self.collectionView?.collectionViewLayout.invalidateLayout() } 

这会导致使用集合视图的新大小再次调用sizeForItemAt 。 在你的情况下,它应该能够更新布局中可用的新约束。

从iOS 10开始,我们已经在stream布局上有了新的API来做到这一点。

你所要做的就是将你的flowLayout.estimatedItemSize设置为一个新的常量UICollectionViewFlowLayoutAutomaticSize

资料来源: http : //asciiwwdc.com/2016/sessions/219

在您的viewDidLayoutSubviews ,将estimatedItemSize设置为全宽(布局指的是UICollectionViewFlowLayout对象):

 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { return CGSize(width: collectionView.bounds.size.width, height: 120) } 

在你的单元格上,确保你的约束同时触及单元格的顶部和底部(下面的代码使用Cartography来简化约束的设置,但是你可以用NSLayoutConstraint或者IB来实现)。

 constrain(self, nameLabel, valueLabel) { view, name, value in name.top == view.top + 10 name.left == view.left name.bottom == view.bottom - 10 value.right == view.right value.centerY == view.centerY } 

瞧,你的细胞现在会自动增长!

没有解决scheme为我工作,因为我需要dynamic宽度来适应iPhone的宽度。

  class CustomLayoutFlow: UICollectionViewFlowLayout { override init() { super.init() minimumInteritemSpacing = 1 ; minimumLineSpacing = 1 ; scrollDirection = .horizontal } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) minimumInteritemSpacing = 1 ; minimumLineSpacing = 1 ; scrollDirection = .horizontal } override var itemSize: CGSize { set { } get { let width = (self.collectionView?.frame.width)! let height = (self.collectionView?.frame.height)! return CGSize(width: width, height: height) } } } class TextCollectionViewCell: UICollectionViewCell { @IBOutlet weak var textView: UITextView! override func prepareForReuse() { super.prepareForReuse() } } class IntroViewController: UIViewController, UITextViewDelegate, UICollectionViewDataSource, UICollectionViewDelegate, UINavigationControllerDelegate { @IBOutlet weak var collectionViewTopDistanceConstraint: NSLayoutConstraint! @IBOutlet weak var collectionViewTopDistanceConstraint: NSLayoutConstraint! @IBOutlet weak var collectionView: UICollectionView! var collectionViewLayout: CustomLayoutFlow! override func viewDidLoad() { super.viewDidLoad() self.collectionViewLayout = CustomLayoutFlow() self.collectionView.collectionViewLayout = self.collectionViewLayout } override func viewWillLayoutSubviews() { self.collectionViewTopDistanceConstraint.constant = UIScreen.main.bounds.height > 736 ? 94 : 70 self.view.layoutIfNeeded() } } 

根据我的评论埃里克的答案,我的解决scheme是非常相似的,但我必须在preferredSizeFor …添加一个约束,以约束到固定的维度。

  override func systemLayoutSizeFitting( _ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize { width.constant = targetSize.width let size = contentView.systemLayoutSizeFitting( CGSize(width: targetSize.width, height: 1), withHorizontalFittingPriority: .required, verticalFittingPriority: verticalFittingPriority) print("\(#function) \(#line) \(targetSize) -> \(size)") return size } 

这个问题有一些重复,我在这里详细回答: https : //stackoverflow.com/a/47424930/2171044 ,并提供了一个工作示例应用程序在这里。

您必须在您的collectionViewController上inheritanceUICollectionViewDelegateFlowLayout类。 然后添加function:

 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize(width: view.frame.width, height: 100) } 

使用它,你有宽度大小的屏幕宽度。

你现在有一个作为tableViewController的行的collectionViewController。

如果您希望每个单元格的高度都是dynamic的,也许您应该为每个需要的单元格创build自定义单元格。