在UICollectionView中自动调整单元格(全部在代码中)

问题描述 投票:0回答:1

使用Swift-5.0,Xcode-10.2,iOS-12.2,

在尝试在UICollectionView中实现单元格的“自动调整”时,我的代码中存在一个问题(使用UICollectionViewFlowLayout作为其布局)。

单元格的高度是基于内容的(并且未知的前期) - 因此我尝试将单元格的高度自动调整(我现在不关心的“宽度”,例如,可以设置为frame.width)。

即使很难,我只使用一个大的UICollectionView-Cell作为下面的例子,我仍然希望保持“出列”的细胞活着,因为后来,将有更多的细胞需要填充大内容。因此,为了使这个例子更简单,我把numberOfItemsInSection保持在1。

此外,对于下面的示例,每个自定义CollectionViewCell都填充了垂直StackView(垂直添加几个标签和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方法给Cell一个非常大的固定高度,那么每件都有效。 (即,“非常大”,我的意思是,当细胞出现时,细胞的高度会变得更大)。

但是如上所述,我不想将单元格的高度设置为固定值(使用UICollectionViewController的方法sizeForItemAt - 而是实现所需的自动调整大小。

为了自动调整单元格,我尝试了以下方法:

方法A)

使用collectionViewFlowLayout.estimatedItemSize = CGSize(...)

(并且不要使用sizeForItemAt方法)

- >同样的事情:如果估计大小设置得足够大,则不会发生错误。如果我将它设置得太小,那么我得到相同的约束错误......

方法B)

使用collectionViewFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

(并使用或不使用sizeForItemAt方法 - 没有区别)

- >同样的错误:当我开始在UICollectionView-cell上滚动时,它会抛出相同的Constraint-error ...

有点有希望的是观察到使用UICollectionViewFlowLayout.automaticSize使应用程序按需工作(除了上面的错误仍然被抛出 - 但奇怪 - 足够,应用程序继续以某种方式运行)。对我来说,应用程序工作并抛出错误是不可接受的。问题是,如何摆脱约束错误?

这是我使用的所有代码:

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

为了抽象出单元组件的autolayout-constraints的锚定和高度定义(即一堆标签和填充单元格的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
    }
}
uicollectionview autolayout constraints uicollectionviewcell autosize
1个回答
0
投票

最后,经过一番挖掘,我找到了一个解决方案:

如果要自动调整自定义UICollectionViewCell,有四件事很重要:

  1. 在创建UICollectionViewController实例时,请确保传递一个将estimatedItemSize属性设置为某些有根据的猜测值的FlowLayout(在我的情况下,高度= 1 [因为未知])
let collectionViewFlowLayout = UICollectionViewFlowLayout()
collectionViewFlowLayout.estimatedItemSize = CGSize(width: UIScreen.main.bounds.width, height: 1)

let vc = TestViewController(collectionViewLayout: collectionViewFlowLayout)

.

  1. 在自定义UICollectionViewCell中创建一个属性,用于跟踪单元格的高度

(即在你的UICollectionViewCell类中,你总是知道单元格的高度是什么(即使稍后动态改变)。再次,我想摆脱我需要在出列前在UICollectionViewController的sizeForItemAt方法中定义这个高度的事实。细胞)

  1. 不要忘记在自定义UICollectionViewCell中添加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
}
  1. 当然,每当您的自定义CollectionViewCell动态更改其高度时,您还需要再次更改myContentHeight属性...

(调用preferredLayoutAttributesFitting你不需要在代码中关心自己,每当用户进入视图,滚动或做任何其他事情时,都会自动调用此方法。操作系统或多或少地关注此...)。

© www.soinside.com 2019 - 2024. All rights reserved.