使用Swift-5.0,Xcode-10.2,iOS-12.2和
我的代码在尝试实现UICollectionView中的单元格的“自动化”(使用UICollectionViewFlowLayout作为其布局)时出现了问题。
单元格的高度是基于内容的(并且是未知的)-因此,我尝试使单元格的高度自动调整(我现在并且可以不用的“宽度”,例如,将其设置为frame.width
。
即使艰难,我在下面的示例中仅使用一个大的UICollectionView-Cell,我仍然希望保持细胞的“出队”状态,因为稍后,将有更多的单元需要填充大内容。因此,为了使该示例更简单,我将numberOfItemsInSection
保持为1。
此外,对于下面的示例,每个自定义CollectionViewCell都填充有一个垂直的StackView(将两个Label和ImageView垂直相加)。 StackView在这里并不重要,但是我只是作为一个简单的例子。再次,自定义CollectionViewCell的内容将在以后更改(特别是它将更改为与内容相关的高度)。下面代码中固定高度的单元格内容仅出于示例原因...
但是我仍然想摆脱这个问题,是关于如何使CollectionViewCell根据内容自动调整其高度的问题(无论是固定高度(如下面的示例)还是动态的基于内容的高度(如未来的实现)?
我不断收到以下约束错误:
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one
you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
如果我使用sizeForItemAt
内的UICollectionViewController
方法给单元格设置一个非常大的固定高度,则一切正常。 (即“非常大”,是指单元出队时比单元的高度大得多)。
但是如上所述,我不想将像元的高度设置为固定值(使用UICollectionViewController的方法sizeForItemAt
-而是希望实现自动调整大小。
要自动调整单元格的大小,我尝试了以下操作:
方法A)
使用collectionViewFlowLayout.estimatedItemSize = CGSize(...)
(并且不要使用sizeForItemAt
方法)
->同样,如果将估计大小设置得足够大,则不会发生错误。如果将其设置得太小,则会出现相同的约束错误...
方法B)
使用collectionViewFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
(并且使用或不使用sizeForItemAt
方法-不会有所作为)
->再次出现相同的错误:一旦我开始在UICollectionView单元上滚动,它将引发相同的约束错误...
观察到使用UICollectionViewFlowLayout.automaticSize
使App能够按预期工作(虽然仍然抛出上述错误-但奇怪的是,无论如何该App仍然可以运行),这一点很有希望。对我来说,该应用程序正常工作并引发错误是不可接受的。问题是,如何摆脱约束错误?
这是我使用的所有代码:
class TestViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let cellId = "cellID"
override func viewDidLoad() {
super.viewDidLoad()
collectionView.backgroundColor = .yellow
collectionView.register(MyCollectionViewCell.self, forCellWithReuseIdentifier: cellId)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MyCollectionViewCell
return cell
}
// func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// return .init(width: view.frame.width, height: 4000)
// }
init() {
let collectionViewFlowLayout = UICollectionViewFlowLayout()
// collectionViewFlowLayout.estimatedItemSize = CGSize(width: UIScreen.main.bounds.width, height: 4000)
collectionViewFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
super.init(collectionViewLayout: collectionViewFlowLayout)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
这是CustomCollectionViewCell:
import UIKit
class MyCollectionViewCell: UICollectionViewCell {
let titleLabel1 = UILabel(text: "Title 123", font: .boldSystemFont(ofSize: 30))
let titleLabel2 = UILabel(text: "Testing...", font: .boldSystemFont(ofSize: 15))
let imgView1 = UIImageView(image: nil)
let imgView2 = UIImageView(image: nil)
let imgView3 = UIImageView(image: nil)
let imgView4 = UIImageView(image: nil)
let imgView5 = UIImageView(image: nil)
let imageView: UIImageView = {
let imgView = UIImageView()
imgView.backgroundColor = .green
return imgView
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .yellow
titleLabel1.constrainHeight(constant: 50)
titleLabel1.constrainWidth(constant: 250)
titleLabel2.constrainHeight(constant: 50)
titleLabel2.constrainWidth(constant: 250)
imgView1.constrainHeight(constant: 100)
imgView1.constrainWidth(constant: 200)
imgView1.backgroundColor = .green
imgView2.constrainHeight(constant: 100)
imgView2.constrainWidth(constant: 200)
imgView2.backgroundColor = .green
imgView3.constrainHeight(constant: 100)
imgView3.constrainWidth(constant: 200)
imgView3.backgroundColor = .green
imgView4.constrainHeight(constant: 100)
imgView4.constrainWidth(constant: 200)
imgView4.backgroundColor = .green
imgView5.constrainHeight(constant: 100)
imgView5.constrainWidth(constant: 200)
imgView5.backgroundColor = .green
let stackView = UIStackView(arrangedSubviews: [titleLabel1, imgView1, titleLabel2, imgView2, imgView3, imgView4, imgView5])
addSubview(stackView)
stackView.anchor(top: safeAreaLayoutGuide.topAnchor, leading: leadingAnchor, bottom: bottomAnchor, trailing: trailingAnchor)
stackView.spacing = 20
stackView.axis = .vertical
stackView.alignment = .center
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
所需的扩展名定义如下:
extension UILabel {
convenience init(text: String, font: UIFont) {
self.init(frame: .zero)
self.text = text
self.font = font
self.backgroundColor = .red
}
}
extension UIImageView {
convenience init(cornerRadius: CGFloat) {
self.init(image: nil)
self.layer.cornerRadius = cornerRadius
self.clipsToBounds = true
self.contentMode = .scaleAspectFill
}
}
为了抽象出单元格组件的自动布局约束的锚定和高度定义(即,填充单元格的一堆标签和imageViews),我使用了以下UIView扩展...:
(扩展名已由Brian Voong发布-参见video link)
// Reference Video: https://youtu.be/iqpAP7s3b-8
extension UIView {
@discardableResult
func anchor(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, padding: UIEdgeInsets = .zero, size: CGSize = .zero) -> AnchoredConstraints {
translatesAutoresizingMaskIntoConstraints = false
var anchoredConstraints = AnchoredConstraints()
if let top = top {
anchoredConstraints.top = topAnchor.constraint(equalTo: top, constant: padding.top)
}
if let leading = leading {
anchoredConstraints.leading = leadingAnchor.constraint(equalTo: leading, constant: padding.left)
}
if let bottom = bottom {
anchoredConstraints.bottom = bottomAnchor.constraint(equalTo: bottom, constant: -padding.bottom)
}
if let trailing = trailing {
anchoredConstraints.trailing = trailingAnchor.constraint(equalTo: trailing, constant: -padding.right)
}
if size.width != 0 {
anchoredConstraints.width = widthAnchor.constraint(equalToConstant: size.width)
}
if size.height != 0 {
anchoredConstraints.height = heightAnchor.constraint(equalToConstant: size.height)
}
[anchoredConstraints.top, anchoredConstraints.leading, anchoredConstraints.bottom, anchoredConstraints.trailing, anchoredConstraints.width, anchoredConstraints.height].forEach{ $0?.isActive = true }
return anchoredConstraints
}
func fillSuperview(padding: UIEdgeInsets = .zero) {
translatesAutoresizingMaskIntoConstraints = false
if let superviewTopAnchor = superview?.topAnchor {
topAnchor.constraint(equalTo: superviewTopAnchor, constant: padding.top).isActive = true
}
if let superviewBottomAnchor = superview?.bottomAnchor {
bottomAnchor.constraint(equalTo: superviewBottomAnchor, constant: -padding.bottom).isActive = true
}
if let superviewLeadingAnchor = superview?.leadingAnchor {
leadingAnchor.constraint(equalTo: superviewLeadingAnchor, constant: padding.left).isActive = true
}
if let superviewTrailingAnchor = superview?.trailingAnchor {
trailingAnchor.constraint(equalTo: superviewTrailingAnchor, constant: -padding.right).isActive = true
}
}
func centerInSuperview(size: CGSize = .zero) {
translatesAutoresizingMaskIntoConstraints = false
if let superviewCenterXAnchor = superview?.centerXAnchor {
centerXAnchor.constraint(equalTo: superviewCenterXAnchor).isActive = true
}
if let superviewCenterYAnchor = superview?.centerYAnchor {
centerYAnchor.constraint(equalTo: superviewCenterYAnchor).isActive = true
}
if size.width != 0 {
widthAnchor.constraint(equalToConstant: size.width).isActive = true
}
if size.height != 0 {
heightAnchor.constraint(equalToConstant: size.height).isActive = true
}
}
func centerXInSuperview() {
translatesAutoresizingMaskIntoConstraints = false
if let superViewCenterXAnchor = superview?.centerXAnchor {
centerXAnchor.constraint(equalTo: superViewCenterXAnchor).isActive = true
}
}
func centerYInSuperview() {
translatesAutoresizingMaskIntoConstraints = false
if let centerY = superview?.centerYAnchor {
centerYAnchor.constraint(equalTo: centerY).isActive = true
}
}
func constrainWidth(constant: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
widthAnchor.constraint(equalToConstant: constant).isActive = true
}
func constrainHeight(constant: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
heightAnchor.constraint(equalToConstant: constant).isActive = true
}
}
答案 0 :(得分:0)
最后,经过进一步的挖掘,我找到了一个解决方案:
如果要自动调整自定义UICollectionViewCell的大小,那么四件事很重要:
estimatedItemSize
属性设置为一些有根据的猜测值(在我的情况下,高度为1 [由于未知])let collectionViewFlowLayout = UICollectionViewFlowLayout()
collectionViewFlowLayout.estimatedItemSize = CGSize(width: UIScreen.main.bounds.width, height: 1)
let vc = TestViewController(collectionViewLayout: collectionViewFlowLayout)
。
(即,在UICollectionViewCell类中,您将始终知道单元格的高度(即使以后会动态更改)。同样,我想摆脱这样一个事实,即我需要在UICollectionViewController的{ {1}}方法,然后将单元出队)
sizeForItemAt
方法:preferredLayoutAttributesFitting
let myContentHeight = CGFloat(720)
// in my above code-example, the 720 are the sum of 2 x 50 (labels) plus 5 x 100 (imageViews) plus 6 x 20 (spacing)
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
var newFrame = layoutAttributes.frame
// myContentHeight corresponds to the total height of your custom UICollectionViewCell
newFrame.size.height = ceil(myContentHeight)
layoutAttributes.frame = newFrame
return layoutAttributes
}
属性... (myContentHeight
的调用您无需在代码中打扰自己,只要用户进入视图,滚动或执行其他任何操作,该方法就会自动调用。操作系统会或多或少地照顾您这个...)。