UITableViewCell内的UICollectionView与UITableViewAutoDimesion

时间:2018-09-20 14:45:00

标签: uitableview uicollectionview uistackview uitableviewautomaticdimension

我正在做一个项目,需要在UITableViewCell内添加一个UICollectionView(水平方向)。 UITableViewCell高度使用UITableViewAutoDimension,每个UITableViewCell我都有一个UIView(带有边框以满足设计要求)作为基本视图,在UIView中,我添加了一个UIStackView作为containerView,以按比例用另外两个按钮按比例填充UICollectionView垂直。然后,对于UICollectionViewCell,我添加了一个UIStackView来填充五个标签。

现在,如果UITableViewDelegate分配了固定的高度,则自动布局将起作用。但是它不适用于UITableViewAutoDimension。我的猜测是,当UITableView呈现其单元格时,UICollectionView框架尚未准备就绪。因此,UITableViewAutoDimension使用默认的UICollectionView高度为零来计算UITableViewCell的高度。

因此,当然,在向互联网提出问题之前,我一直在进行搜索,但是没有解决方案对我有用。这是我尝试过的一些链接。

UICollectionView inside a UITableViewCell — dynamic height?

Making UITableView with embedded UICollectionView using UITableViewAutomaticDimension

UICollectionView inside UITableViewCell does NOT dynamically size correctly

有人有同样的问题吗?如果以上链接有效,请随时告诉我,以防万一我做错了。谢谢

--- 更新于2018年9月23日

  • 布局可视化: UI进行了一些修改,但是并没有改变我面临的问题。希望图片能有所帮助。

    enter image description here

  • 代码: 我拥有的当前代码实际上未在UITableViewCell中使用UIStackView,并且UITableView heightForRowAtIndex我返回的固定高度为250.0。但是,如果我按问题中所述返回UITableViewAutoDimension,则UITableView无法正确配置像元高度。

1。 UITableViewController

class ViewController: UIViewController {

    private let tableView: UITableView = {
       let tableView = UITableView()
       tableView.translatesAutoresizingMaskIntoConstraints = false
       tableView.register(ViewControllerTableViewCell.self, forCellReuseIdentifier: ViewControllerTableViewCell.identifier)
       return tableView
    }()

    private lazy var viewModel: ViewControllerViewModel = {
        return ViewControllerViewModel(models: [
            Model(title: "TITLE", description: "SUBTITLE", currency: "USD", amount: 100, summary: "1% up"),
            Model(title: "TITLE", description: "SUBTITLE", currency: "USD", amount: 200, summary: "2% up"),
            Model(title: "TITLE", description: "SUBTITLE", currency: "USD", amount: 300, summary: "3% up"),
        ])
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.dataSource = self
        tableView.delegate = self

        view.addSubview(tableView)

        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

extension ViewController: UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int {
        return viewModel.numberOfSections
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return viewModel.numberOfRowsInSection
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: ViewControllerTableViewCell.identifier, for: indexPath) as! ViewControllerTableViewCell
        cell.configure(viewModel: viewModel.cellViewModel)
        return cell
    }
}

extension ViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 250.0
    }
}

2。 UITableViewCell

class ViewControllerTableViewCell: UITableViewCell {

    static let identifier = "ViewControllerTableViewCell"

    private var viewModel: ViewControllerTableViewCellViewModel!

    private let borderView: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.layer.borderColor = UIColor.black.cgColor
        view.layer.borderWidth = 1
        return view
    }()

    private let stackView: UIStackView = {
        let stackView = UIStackView()
        stackView.axis = .vertical
        stackView.distribution = .fillProportionally
        return stackView
    }()

    private let seperator: UIView = {
        let seperator = UIView()
        seperator.translatesAutoresizingMaskIntoConstraints = false
        seperator.backgroundColor = .lightGray
        return seperator
    }()

    private let actionButton: UIButton = {
        let button = UIButton()
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setTitle("Show business insight", for: .normal)
        button.setTitleColor(.black, for: .normal)
        return button
    }()

    private let pageControl: UIPageControl = {
        let pageControl = UIPageControl()
        pageControl.translatesAutoresizingMaskIntoConstraints = false
        pageControl.pageIndicatorTintColor = .lightGray
        pageControl.currentPageIndicatorTintColor = .black
        pageControl.hidesForSinglePage = true
        return pageControl
    }()

    private let collectionView: UICollectionView = {
        let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal
        let collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: 200, height: 200), collectionViewLayout: layout)
        collectionView.isPagingEnabled = true
        collectionView.backgroundColor = .white
        collectionView.showsHorizontalScrollIndicator = false
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        collectionView.register(ViewControllerCollectionViewCell.self, forCellWithReuseIdentifier: ViewControllerCollectionViewCell.identifier)
        return collectionView
    }()

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setUpConstraints()
        setUpUserInterface()
    }

    func configure(viewModel: ViewControllerTableViewCellViewModel) {
        self.viewModel = viewModel
        pageControl.numberOfPages = viewModel.items.count
        collectionView.reloadData()
    }

    @objc func pageControlValueChanged() {
        let indexPath = IndexPath(item: pageControl.currentPage, section: 0)
        collectionView.scrollToItem(at: indexPath, at: .left, animated: true)
    }

    private func setUpConstraints() {
        contentView.addSubview(borderView)
        borderView.addSubview(actionButton)
        borderView.addSubview(seperator)
        borderView.addSubview(pageControl)
        borderView.addSubview(collectionView)

        NSLayoutConstraint.activate([
            borderView.topAnchor.constraint(equalTo: topAnchor, constant: 10),
            borderView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
            borderView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20),
            borderView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10),
        ])

        NSLayoutConstraint.activate([
            actionButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 44),
            actionButton.leadingAnchor.constraint(equalTo: borderView.leadingAnchor),
            actionButton.trailingAnchor.constraint(equalTo: borderView.trailingAnchor),
            actionButton.bottomAnchor.constraint(equalTo: borderView.bottomAnchor)
        ])

        NSLayoutConstraint.activate([
            seperator.heightAnchor.constraint(equalToConstant: 1),
            seperator.leadingAnchor.constraint(equalTo: borderView.leadingAnchor),
            seperator.trailingAnchor.constraint(equalTo: borderView.trailingAnchor),
            seperator.bottomAnchor.constraint(equalTo: actionButton.topAnchor)
        ])

        NSLayoutConstraint.activate([
            pageControl.heightAnchor.constraint(greaterThanOrEqualToConstant: 40),
            pageControl.leadingAnchor.constraint(equalTo: borderView.leadingAnchor),
            pageControl.trailingAnchor.constraint(equalTo: borderView.trailingAnchor),
            pageControl.bottomAnchor.constraint(equalTo: seperator.topAnchor)
        ])

        NSLayoutConstraint.activate([
            collectionView.topAnchor.constraint(equalTo: borderView.topAnchor),
            collectionView.leadingAnchor.constraint(equalTo: borderView.leadingAnchor),
            collectionView.trailingAnchor.constraint(equalTo: borderView.trailingAnchor),
            collectionView.bottomAnchor.constraint(equalTo: pageControl.topAnchor)
        ])
    }

    private func setUpUserInterface() {
        selectionStyle = .none
        collectionView.delegate   = self
        collectionView.dataSource = self
        pageControl.addTarget(self, action: #selector(pageControlValueChanged), for: .valueChanged)
    }
}

extension ViewControllerTableViewCell: UICollectionViewDataSource {
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return viewModel!.numberOfSections
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return viewModel!.numberOfRowsInSection
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ViewControllerCollectionViewCell.identifier, for: indexPath) as! ViewControllerCollectionViewCell
        let collectionCellViewModel = viewModel!.collectionCellViewModel(at: indexPath)
        cell.configure(viewModel: collectionCellViewModel)
        return cell
    }
}

extension ViewControllerTableViewCell: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        debugPrint("did select \(indexPath.row)")
    }

    func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        pageControl.currentPage = indexPath.row
    }
}

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

    func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        insetForSectionAt section: Int) -> UIEdgeInsets {
        return UIEdgeInsets(top: 0, left: 20.0, bottom: 0, right: 20.0)
    }

    func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 40.0
    }
}

3。 UICollectionViewCell

class ViewControllerCollectionViewCell: UICollectionViewCell {

    override class var requiresConstraintBasedLayout: Bool {
        return true
    }

    static let identifier = "ViewControllerCollectionViewCell"

    private let stackView: UIStackView = {
        let stackView = UIStackView()
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.axis = .vertical
        stackView.distribution = .fillEqually
        return stackView
    }()

    private let titleLabel: UILabel = {
        let titleLabel = UILabel()
        titleLabel.textColor = .black
        titleLabel.font = UIFont.systemFont(ofSize: 20, weight: .bold)
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        return titleLabel
    }()

    private let descriptionLabel: UILabel = {
        let descriptionLabel = UILabel()
        descriptionLabel.textAlignment = .right
        descriptionLabel.textColor = .black
        descriptionLabel.translatesAutoresizingMaskIntoConstraints = false
        return descriptionLabel
    }()

    private let amountLabel: UILabel = {
        let amountLabel = UILabel()
        amountLabel.textColor = .black
        amountLabel.textAlignment = .right
        amountLabel.translatesAutoresizingMaskIntoConstraints = false
        return amountLabel
    }()

    private let summaryLabel: UILabel = {
        let summaryLabel = UILabel()
        summaryLabel.textColor = .black
        summaryLabel.textAlignment = .right
        summaryLabel.translatesAutoresizingMaskIntoConstraints = false
        return summaryLabel
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        contentView.addSubview(stackView)
        stackView.addArrangedSubview(titleLabel)
        stackView.addArrangedSubview(descriptionLabel)
        stackView.addArrangedSubview(amountLabel)
        stackView.addArrangedSubview(summaryLabel)

        NSLayoutConstraint.activate([
            stackView.topAnchor.constraint(equalTo: topAnchor),
            stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
            stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
            stackView.bottomAnchor.constraint(equalTo: bottomAnchor)
        ])
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func configure(viewModel: CollectionCellViewModel) {
        titleLabel.text = viewModel.title
        descriptionLabel.text = viewModel.description
        amountLabel.text = viewModel.amount.localizedCurrencyString(with: viewModel.currency)
        summaryLabel.text = viewModel.summary
    }
}

1 个答案:

答案 0 :(得分:0)

要启用自定义表格视图单元格,必须将表格视图的rowHeight属性设置为UITableViewAutomaticDimension。您还必须将一个值分配给estimateRowHeight属性,还需要一个不间断的约束和视图链来填充单元格的内容视图。

更多:https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithSelf-SizingTableViewCells.html#//apple_ref/doc/uid/TP40010853-CH25-SW1

因此,在这种情况下,带有边框的UIView动态确定表格视图单元格的高度,您必须告诉系统该UIView有多高,您可以将此视图约束到UIStackView边缘(顶部,底部,前导,尾随) ,并使用UIStackView 内在内容高度作为UIView(带边框)高度。

问题出在UIStackView内部内容的高度以及具有distribution属性。 UIStackView可以自动估计两个UIButton的高度(基于文本的大小,字体样式和字体大小),但是UICollectionView没有内在的内容高度,并且由于UIStackView是按比例填充的,因此UIStackView会设置一个高度UICollectionView的值为0.0,所以看起来像这样:

Fill Proportionally UIStackView

但是,如果您将UIStackView分发属性更改为均等填充,您将具有以下内容:

Fill Equally UIStackView

如果希望UICollectionView确定其自身大小并按比例填充UIStackView,则需要为UICollectionView设置高度限制。然后根据UICollectionViewCell的内容高度更新约束。

您的代码看起来不错,您只需要进行少量更改, 这是一个解决方案(2018年9月25日更新):

 private func setUpConstraints() {
    contentView.addSubview(borderView)
    borderView.addSubview(actionButton)
    borderView.addSubview(seperator)
    borderView.addSubview(pageControl)
    borderView.addSubview(collectionView)

    NSLayoutConstraint.activate([
        borderView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10), // ---  >. Use cell content view anchors
        borderView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20), // ---  >. Use cell content view anchors
        borderView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20), // ---  >. Use cell content view anchors
        borderView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10), // ---  >. Use cell content view anchors
        ])


    //other constraints....

    NSLayoutConstraint.activate([
        collectionView.topAnchor.constraint(equalTo: borderView.topAnchor),
        collectionView.leadingAnchor.constraint(equalTo: borderView.leadingAnchor),
        collectionView.trailingAnchor.constraint(equalTo: borderView.trailingAnchor),
        collectionView.bottomAnchor.constraint(equalTo: pageControl.topAnchor),

    collectionView.heightAnchor.constraint(equalToConstant: 200) // ---> System needs to know height, add this constraint to collection view.
        ])
}

还请记住将tableView rowHeight设置为UITableView.automaticDimension,并使用tableView.estimatedRowHeight为系统提供估计的行高。