UICollectionView внутри UITableViewCell с UITableViewAutoDimesion

Я работаю над проектом необходимо добавить UICollectionView(горизонтальное направление) внутри UITableViewCell. Высота UITableViewCell использует UITableViewAutoDimension, и каждый UITableViewCell имеет UIView (с рамкой для требований к дизайну) в качестве базового представления, а в UIView добавлен UIStackView в качестве контейнера, чтобы пропорционально заполнить UICollectionView двумя другими кнопками вертикально. И затем для UICollectionViewCell я добавил в UIStackView заполнить пять меток.

Теперь автоматическое расположение работает, если UITableViewDelegate назначает фиксированную высоту. Но это не работает с UITableViewAutoDimension. Я предполагаю, что кадр UICollectionView не готов, пока UITableView рендерит свои ячейки. Таким образом, UITableViewAutoDimension вычисляет высоту UITableViewCell с высотой по умолчанию UICollectionView, которая равна нулю.

Так что, конечно, я искал, прежде чем выкинуть вопрос в Интернет, но никакие решения не помогли мне. Вот несколько ссылок, которые я пробовал.

UICollectionView внутри UITableViewCell - динамическая высота?

Создание UITableView со встроенным UICollectionView с использованием UITableViewAutomaticDimension

UICollectionView внутри UITableViewCell НЕ динамически изменяет размер

У кого-нибудь есть такая же проблема? Если ссылки выше работали, пожалуйста, дайте мне знать, если я сделал это неправильно. Спасибо

--- Обновлено 23 сентября 2018 года:

  • Визуализация макета: есть некоторые модификации пользовательского интерфейса, но это не меняет проблему, с которой я сталкиваюсь. Надеюсь, что картина может помочь.

    введите описание изображения здесь

  • Код: текущий код, который у меня есть, фактически не использует UIStackView в UITableViewCell и UITableView heightForRowAtIndex. Я возвращаю фиксированную высоту с 250.0. Однако UITableView не настроит высоту ячейки должным образом, если я верну UITableViewAutoDimension, как я упоминал в своем вопросе.

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 ответ

Чтобы включить ячейки табличного представления с самоопределением размера, необходимо установить для свойства rowHeight табличного представления значение UITableViewAutomaticDimension. Вы также должны присвоить значение свойству оценкам RowHeight, вам также нужна непрерывная цепочка ограничений и представлений, чтобы заполнить представление содержимого ячейки.

Подробнее: https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithSelf-SizingTableViewCells.html

Таким образом, в этом случае UIView с рамкой динамически определяет высоту ячейки табличного представления, вы должны сообщить системе, какова высота этого UIView, вы можете ограничить это представление краями UIStackView (верх, низ, ведущий, задний) и использовать высота внутреннего содержимого UIStackView как высота UIView (с рамкой).

Проблема заключается в внутренней высоте содержимого UIStackView и в свойстве распределения. UIStackView может автоматически оценивать высоту для двух кнопок UIB (в зависимости от размера текста, стиля шрифта и размера шрифта), но UICollectionView не имеет внутренней высоты содержимого, а поскольку ваш UIStackview заполняется пропорционально, UIStackView устанавливает высоту 0.0 для UICollectionView, так что это выглядит примерно так:

Заполнить пропорционально UIStackView

Но если вы измените свойство распределения UIStackView для одинакового заполнения, у вас будет что-то вроде этого:

Заполните одинаково UIStackView

Если вы хотите, чтобы UICollectionView определял свой собственный размер и заполнял UIStackView пропорционально, вам нужно установить ограничение высоты для UICollectionView. А затем обновите ограничение на основе высоты содержимого UICollectionViewCell.

Ваш код выглядит хорошо, вам нужно всего лишь внести незначительные изменения, вот решение (обновлено 25.09.2008):

 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.

Другие вопросы по тегам