iOS 11: адаптируйте размер ячейки к содержимому с помощью UITableViewAutomaticDimension

Я хочу, чтобы ячейки UITableView адаптировались к размеру их содержимого в iOS 10 и 11 с помощью:

tableView.estimatedRowHeight = UITableViewAutomaticDimension // default in iOS 11
tableView.rowHeight = UITableViewAutomaticDimension

Без установки tableView.rowHeight на явное числовое значение, которое является новым значением по умолчанию в iOS 11.

UIView не имеет внутреннего размера содержимого, поэтому я установил ограничение макета для его привязки по высоте. Однако этот якорь ломается во время выполнения.

Какие внутренние ограничения в UITableViewCell необходимы, чтобы ячейка могла адаптироваться к своему содержимому?


Это работает только в iOS 11:

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        let tableView = UITableView()
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        tableView.estimatedRowHeight = UITableViewAutomaticDimension
        tableView.rowHeight = UITableViewAutomaticDimension
        tableView.delegate = self
        tableView.dataSource = self
        tableView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(tableView)

        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.topAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            tableView.leftAnchor.constraint(equalTo: view.leftAnchor),
            tableView.rightAnchor.constraint(equalTo: view.rightAnchor)
            ])
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: "cell")

        let view = UIView()
        view.backgroundColor = .blue
        view.translatesAutoresizingMaskIntoConstraints = false
        cell.contentView.addSubview(view)

        NSLayoutConstraint.activate([
            view.topAnchor.constraint(equalTo: cell.contentView.topAnchor),
            view.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor),
            view.leftAnchor.constraint(equalTo: cell.contentView.leftAnchor),
            view.rightAnchor.constraint(equalTo: cell.contentView.rightAnchor),
            view.heightAnchor.constraint(equalToConstant: 300)
            ])

        return cell
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

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

В iOS 10 возникает эта ошибка времени выполнения, и размер ячейки не адаптируется:

[LayoutConstraints] Невозможно одновременно удовлетворить ограничения. Вероятно, по крайней мере, одно из ограничений в следующем списке вам не нужно. Попробуйте следующее: (1) посмотрите на каждое ограничение и попытайтесь выяснить, чего вы не ожидаете; (2) найдите код, который добавил нежелательное ограничение или ограничения, и исправьте его. ("NSLayoutConstraint: 0x17009c020 V: | - (0) - [test.MyView: 0x11dd1acb0] (активно, имена: '|': UITableViewCellContentView: 0x11dd15220)», «NSLayoutConstraint: 0x17009c160 = MydView: 0x11dd15220.bottom (активный) "," NSLayoutConstraint: 0x17009c390 test.MyView: 0x11dd1acb0.height == 300 (активный) "," NSLayoutConstraint: 0x17009be40 'UIView-Encapsulated-Layout-Height' UITableView2: ) ")

Попытается восстановить, нарушив ограничение NSLayoutConstraint: 0x17009c390 test.MyView: 0x11dd1acb0.height == 300 (активно)

Сделайте символическую точку останова в UIViewAlertForUnsatisfiableConstraints, чтобы отловить это в отладчике. Также могут быть полезны методы из категории UIConstraintBasedLayoutDebugging в UIView, перечисленные в UIKit / UIView.h.


Это работает в iOS 10 и 11, но не использует новый подход iOS 11, поскольку tableView.estimatedRowHeight не UITableViewAutomaticDimension:

class ViewController_TableView: UIViewController, UITableViewDataSource, UITableViewDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        let tableView = UITableView()
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        tableView.estimatedRowHeight = 30
        tableView.rowHeight = UITableViewAutomaticDimension
        tableView.delegate = self
        tableView.dataSource = self
        tableView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(tableView)

        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.topAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            tableView.leftAnchor.constraint(equalTo: view.leftAnchor),
            tableView.rightAnchor.constraint(equalTo: view.rightAnchor)
            ])
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: "cell")

        let view = MyView()
        view.backgroundColor = .blue
        view.translatesAutoresizingMaskIntoConstraints = false
        cell.contentView.addSubview(view)

        NSLayoutConstraint.activate([
            view.topAnchor.constraint(equalTo: cell.contentView.topAnchor),
            view.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor),
            view.leftAnchor.constraint(equalTo: cell.contentView.leftAnchor),
            view.rightAnchor.constraint(equalTo: cell.contentView.rightAnchor)
            ])

        return cell
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

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

class MyView: UIView {

    override var intrinsicContentSize: CGSize {
        get {
            return CGSize(width: 300, height: 300)
        }
    }
}

person Manuel    schedule 03.10.2017    source источник
comment
вот несколько рабочих примеров: raywenderlich.com/129059/self-sizing -table-view-cells, stackoverflow.com/questions/18746929/; Фактическая функция ожидает, что вы установите все вертикальные ограничения от верха ячейки до низа, если вы пропустили это в IB.   -  person holex    schedule 03.10.2017
comment
@holex Я хочу знать, почему простой пример кода в моем сообщении не работает. Код работает, когда rowHeight не установлено в UITableViewAutomaticDimension, что не так, как предполагается в iOS 11. Ваша ссылка - это пример до iOS 11.   -  person Manuel    schedule 03.10.2017
comment
в iOS11, если вы установите приоритет ограничения высоты, например, 999.9, что немедленно решит проблему прерывания работы; но на самом деле это не объяснение того, почему ограничение нарушается в первую очередь ... (это может быть связано с представлением или округлением плавающих значений, но это мое безумное предположение) , однако ваш пример также является довольно гипотетическим, поскольку никто на самом деле не создает пользовательский интерфейс, подобный этому, на практике, поэтому, вероятно, никому на самом деле не нужно сталкиваться с такой проблемой, как когда-либо.   -  person holex    schedule 03.10.2017
comment
@holex Почему вы говорите, что на самом деле никто не создает такой интерфейс? Не могли бы вы еще раз взглянуть на мой вопрос? Я думаю, что мой первоначальный пост был немного запутанным.   -  person Manuel    schedule 03.10.2017
comment
потому что это очень противоречит шаблону в коммерческой среде; нравиться. например если вам нужно создать довольно сложный пользовательский интерфейс, а затем поддерживать или изменять его, это займет гораздо больше времени, чем создание ячейки прототипа в IB и внесение изменений в редакторе (особенно если ваше приложение использует, например, 25 ячеек прототипа) - Я должен признать, что играть с таким пользовательским интерфейсом очень весело, но если кто-то сделает это в реальной коммерческой среде, этот человек будет серьезно некомпетентен. без обид конечно :)   -  person holex    schedule 03.10.2017
comment
@holex Понятно, пример, конечно, урезан, чтобы сосредоточиться на проблеме. Ячейку можно разделить на подклассы и добавить UIView отдельно, но это не должно иметь никакого значения для ее поведения в этом примере. Лично я стараюсь по возможности не использовать IB, потому что считаю, что ручной код обеспечивает лучшую ремонтопригодность.   -  person Manuel    schedule 03.10.2017
comment
на самом деле это немного отличается с одной конкретной точки зрения: когда ячейка загружается из файла xib, предопределенные ограничения загружаются уже правильно во время инициализации, но когда вы создали ячейку программно, после инициализации в ячейке вообще нет ограничений завершено, поэтому iOS добавляет ограничения по умолчанию в ячейку, и они, похоже, мешают ограничениям, которые вы пытаетесь добавить после, и это может быть прямым ответом на ваши проблемы: почему пользовательский интерфейс не должен создавать так, потому что в вашем собственном примере ремонтопригодность намного хуже.   -  person holex    schedule 03.10.2017
comment
не поймите меня неправильно, я здесь вообще не чей-то руководитель :) но я вряд ли думаю, что кто-то из моей команды сможет доказать ваше утверждение о лучшей ремонтопригодности такого пользовательского интерфейса - как его временной сложности доказуемо хуже.   -  person holex    schedule 03.10.2017
comment
@holex Я думаю, что все, что можно сделать в IB, должно также быть возможным в коде. В противном случае это было бы ошибкой. Я опубликовал примитивный пример, демонстрирующий проблему. ИБ здесь не обсуждается. И я ценю ваши комментарии, размышления всегда хороши :) Также я добавил еще один пример кода, чтобы прояснить проблему.   -  person Manuel    schedule 03.10.2017
comment
это правда, вы могли бы сделать это обоими способами :) но способность не означает, что оба являются практическим выбором ... Я не собираюсь вертеть в тебе нож, а только завершаю свои размышления: мы потратили целое утро, чтобы решить проблему, чего бы вообще не было, если бы вы использовали IB - так что, я могу сказать, что это была пустая трата ресурсов, потому что другой разработчик мог бы закончить весь пользовательский интерфейс в том же самом время, пока кто-то изо всех сил пытается найти решения в иллюзии лучшей ремонтопригодности :), что не очень хорошо в любой коммерческой среде :(   -  person holex    schedule 03.10.2017
comment
@holex, у разработчика, который сбегает в IB из-за непонимания механизмов верстки iOS, совсем другая проблема. Сравнение коммерческой эффективности различных концепций разработки - интересная тема, но я здесь, чтобы найти ответ на свой вопрос.   -  person Manuel    schedule 03.10.2017
comment
да, и это верно и с этой точки зрения: разработчик, который думает, создает ли пользовательский интерфейс в коде, который, как им кажется, понимает жизненный цикл представления, также является реальной проблемой . кстати, я рад, что ты нашел свое решение :)   -  person holex    schedule 03.10.2017
comment
У меня была такая же проблема в моем приложении, где я построил все в раскадровке (а не в коде), а в iOS 10 он работал нормально, никаких предупреждений об ограничениях для моего фиктивного представления, которое находится внутри ячейки динамической высоты, чтобы установить его минимальную высоту . Но как только я скомпилировал iOS 11, он начал выдавать предупреждения. Установка приоритета ограничения высоты фиктивного вида на 999 устранила предупреждение, как предлагалось в комментарии @holex.   -  person KBog    schedule 10.01.2018


Ответы (1)


вы не соответствуете делегату своему контроллеру. затем используйте код

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    let params : [String: Any] = ["q": "dd", "format": "json", "pretty": 1,"no_html": 1, "skip_disambig": 1]
    Alamofire.request("https://api.duckduckgo.com", method: .get, parameters: params).responseJSON { (responseData) in
        print(responseData)
    }

    let tableView = UITableView()
    tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
    tableView.estimatedRowHeight = 40
    tableView.delegate = self
    tableView.dataSource = self
    tableView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(tableView)

    NSLayoutConstraint.activate([
        tableView.topAnchor.constraint(equalTo: view.topAnchor),
        tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        tableView.leftAnchor.constraint(equalTo: view.leftAnchor),
        tableView.rightAnchor.constraint(equalTo: view.rightAnchor)
        ])

}

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

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: "cell")

    let view = UIView()
    view.backgroundColor = .blue
    view.translatesAutoresizingMaskIntoConstraints = false
    cell.contentView.addSubview(view)

    NSLayoutConstraint.activate([
        view.topAnchor.constraint(equalTo: cell.contentView.topAnchor),
        view.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor),
        view.leftAnchor.constraint(equalTo: cell.contentView.leftAnchor),
        view.rightAnchor.constraint(equalTo: cell.contentView.rightAnchor),
        view.heightAnchor.constraint(equalToConstant: 300) // this breaks on run time
        ])

    return cell
}

func numberOfSections(in tableView: UITableView) -> Int {
    return 1
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 1
}
// use this delegate 
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}


}
person Asaduzzaman Shuvro    schedule 03.10.2017
comment
Метод делегата не требуется, если свойство установлено. Его даже не рекомендуется использовать из-за снижения производительности для табличных представлений с большим количеством ячеек. - person Manuel; 03.10.2017
comment
Я согласен. но я думаю, что если вы не используете этот метод делегата, вам нужно установить приоритет сжатия или сжатия контента. - person Asaduzzaman Shuvro; 03.10.2017
comment
Можете ли вы повторно опубликовать мой код с необходимыми приоритетами объятия или сжатия контента? Я сомневаюсь, что это как-то связано с этим. Функция делегата только переопределяет свойство. - person Manuel; 03.10.2017
comment
Я просто замечаю, что вы установили tableView.estimatedRowHeight = 40, пожалуйста, прочтите мой пост еще раз. Я имею в виду именно iOS 11, где по умолчанию этим свойством является `UITableViewAutomaticDimension '. - person Manuel; 03.10.2017
comment
Я проверил ваш код на симуляторе xcode 9 и ios11. он работает отлично. и обратите внимание, что uiview имеет внутреннее содержимое. вы должны использовать uilabel или uimage. тогда я думаю, что это должно работать отлично. :) - person Asaduzzaman Shuvro; 03.10.2017
comment
Да, я уточнил свой вопрос, ищу код, который работает в iOS 10 и 11, но с подходом UITableViewAutomaticDimension. Мой старый код работал в iOS 10, а не в iOS 11, потому что значение по умолчанию tableView.estimatedRowHeight изменилось. Сейчас я пытаюсь адаптировать код к этому подходу, но в iOS 10 он не работает. - person Manuel; 03.10.2017
comment
Я перефразировал свой вопрос, чтобы яснее отразить суть проблемы. - person Manuel; 03.10.2017