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自定义单元格。
- dynamic设置tableHeaderView高度
- iOS布局animation在GMSMapView拖动时忽略持续时间
- iOS 6 Beta中的自动布局
- 我怎样才能使用Autolay编程在一个现有的UIView的顶部的UIView?
- 使用Autolayout扩展tableView单元格导致UIViewAlertForUnsatisfiableConstraints
- AutoLayout约束问题与意外的NSAutoresizingMaskLayoutConstraint
- IOS 8图像缩放和自动布局
- 更正UITableViewHeaderFooterView的子类化和自动布局
- 在iOS 8中自动调整UITableViewCell的大小