【问题标题】:UICollectionView inside UITableViewCell with UITableViewAutoDimesionUITableViewCell 内的 UICollectionView 与 UITableViewAutoDimesion
【发布时间】:2018-09-20 14:45:00
【问题描述】:

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

现在,如果 UITableViewDelegate 分配了固定高度,则自动布局可以工作。但它不适用于 UITableViewAutoDimension。我的猜测是 UICollectionView 框架在 UITableView 渲染它的单元格时还没有准备好。所以 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 修改,但这并没有改变我面临的问题。希望图片能帮到你。

  • 代码: 我当前的代码实际上没有使用 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
    }
}

【问题讨论】:

    标签: uitableview uicollectionview uistackview uitableviewautomaticdimension


    【解决方案1】:

    要启用自我调整大小的表格视图单元格,您必须将表格视图的 rowHeight 属性设置为 UITableViewAutomaticDimension。您还必须为estimatedRowHeight 属性分配一个值,您还需要一个完整的约束链和视图来填充单元格的内容视图。

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

    所以在这种情况下,带边框的 UIView 动态决定了表格视图单元格的高度,你必须告诉系统这个 UIView 有多高,你可以将此视图约束到 UIStackView 边缘(顶部、底部、前导、尾随) ,并使用 UIStackView intrinsic content height 作为 UIView(withborder) 高度。

    问题在于 UIStackView 固有的内容高度和分布属性。 UIStackView 可以自动估计两个 UIButtons 的高度(基于文本大小、字体样式和字体大小),但是 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。

    【讨论】:

    • 首先,感谢您的出色解释和详细信息。我同意你的看法,想想你说的是对的。但是我试图给 UITableViewCell 中的垂直 UIStackView 一个恒定的高度,但不知何故 UITableViewAutoDimension 没有为 UITableViewCell 分配一个合适的高度。我将上传我的代码和布局指南。请帮我看看我是否做得对。谢谢。
    • 嗨,蒂姆!我查看了你的代码,实际上我做了一个 Xcode 项目,效果很好(只需要为集合视图创建高度约束)
    • 嗨 mxlbx!谢谢回复。我实际上知道为 UICollectionView 设置高度约束会起作用。但我们的想法是,我们需要在这个项目中为 UICollectionViewCell 提供动态高度。所以我不能真正给 UICollectionView 一个恒定的高度,除非我计算每个单元格的高度。但是,我的其他团队成员不喜欢自己计算高度,而是利用 Auto-layout 和 StackView 通过其内容计算高度。但他们不确定这是否可能,所以我在这个问题上坚持了好几天。你觉得有可能吗?
    • 您是否尝试使用滚动视图内容大小(collectionView.contentLayoutGuide)或框架(collectionView.frameLayoutGuide)设置高度?自动布局需要以某种方式知道 UICollectionView 的大小。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-07
    • 2014-07-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多