iOS8 теперь изменяет высоту динамической ячейки, пересматривая внутреннее содержимое ячейки

Вот динамическая ячейка

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

Примечание. В примере текст не управляется данными. Это просто текст, локальный для ячейки (скажем, текст справки). Во время выполнения измените текст UILabel с одного слова на несколько строк, используя кнопку внутри ячейки. iOS точно изменяет размер ячейки и таблицы....

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

Как предупредить представление таблицы, чтобы пересчитать все «сейчас»?


(Обратите внимание, что этот вопрос ТОЛЬКО в случае iOS8+, Xcode7+, автомакета для динамической высоты ячеек.)


person Fattie    schedule 19.09.2015    source источник
comment
эмм, я думаю, вы должны как-то активировать автомакет, когда вы назначаете новый текст метке, и внутренняя магия отдыхает. Или там что-то не так однозначно?   -  person sage444    schedule 22.09.2015
comment
Я добавил объяснение, почему вы никогда не должны изменять содержимое ячейки, а также добавил решение о том, как это сделать.   -  person Jiri Trecak    schedule 22.09.2015


Ответы (5)


Я предполагаю, что вы не устанавливаете свойство text UILabel внутри cellForRowAtIndexPath, а где-то еще (или делаете это асинхронно). Если это так, я бы не обновлял там пользовательский интерфейс. Скорее, я бы обновил модель, поддерживающую таблицу, а затем вызвал бы reloadRowsAtIndexPaths. Это позволит снова вызвать cellForRowAtIndexPath, но в отличие от перезагрузки всей таблицы, это изящно сохранит contentOffset табличного представления там, где оно есть.

Я знаю, что все это звучит излишне сложно, но суть в том, что это представление принадлежит не вам, а табличному представлению. Он должен делать всевозможные вещи помимо обновления ячейки. То есть, если ячейка выросла, выясните, какие ячейки прокручиваются вне поля зрения, и удалите их из очереди. Если ячейка уменьшилась, выясните, какие ячейки в результате прокрутились.

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

person Rob    schedule 22.09.2015
comment
Понял, это имеет смысл. На самом деле вы просто не можете заставить клетку что-то делать с собой. В конце концов, это сущность, которая может быть или не быть на экране, может быть сформирована или переформирована в любое время из прототипов и т. д. (Re setNeedsLayout, похоже, ничего не делает!) - person Fattie; 22.09.2015
comment
В полном раскрытии, бывают случаи, когда люди обновляют ячейку напрямую, асинхронно. (Наиболее распространенный пример включает в себя все эти различные категории UIImageView для асинхронной загрузки изображений.) Но они работают, только если высота ячейки не изменяется. И с архитектурной точки зрения это неправильный подход. Гораздо лучше обновить модель, а затем вызвать reloadRowsAtIndexPaths. - person Rob; 22.09.2015

Изменение высоты

Итак, в основном, есть два способа сделать это:

Первый - фактически перезагрузить ячейку (а не таблицу). Перезагрузка вызовет новый heightForRow (не забудьте очистить кеш, если вы кешируете размеры), который вернет правильную новую высоту:

 let indexPaths = [NSIndexPath(forRow: ~the rows in question~, inSection: 0)]
 self.table.reloadRowsAtIndexPaths(indexPaths, withRowAnimation: .Automatic)

(Обратите внимание, однако, что это часто включает перезагрузку более одной строки; особенно если вы выбираете/отменяете выбор, вам нужно перезагружать все измененные строки.)

Однако, если вы ТОЛЬКО хотите изменить размер ячейки и содержимое как таковое и на самом деле не изменили содержимое данных... например:

  • вы нажали какую-то кнопку и назначили новый локальный текст в ячейке для розеток (возможно, текст справки):

  • вы изменили только МАКЕТ ячейки. например, вы увеличили шрифт или изменили поле блока текста, чтобы изменилась высота блока текста, поэтому изменилась высота всей ячейки:

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

 self.table.beginUpdates()
 self.table.endUpdates()

Верное решение

Я вижу, в чем твоя проблема. Вы пытаетесь изменить высоту ячейки от фактической ячейки - но у вас это не получится -> и вы не должны. Видите ли, ячейка — это представление, а представление не должно иметь никакого представления о своих данных — представление — это то, что представляет. Если вам нужны какие-либо изменения, вы должны сообщить об этом своему контролеру. Для этого можно использовать уведомления, но желательно протоколы/делегаты.

Итак, сначала вы создаете протокол в своей ячейке, который будет использоваться для информирования контроллера о том, что произошло изменение:

 protocol MyCellDelegate {

    func buttonTappedForCell(cell : UITableViewCell)
 }

Теперь вам нужно соответствовать этому протоколу в вашем контроллере представления, который содержит таблицу:

 class MyClassWithTableView : MyCellDelegate

Наконец, вам нужно объявить делегата в ячейке:

 class MyCell {
     var delegate : MyCellDelegate
 }

И назначить его в конфигурации ячейки, которая у вас наверняка есть в контроллере представления:

 cell.delegate = self

На самом деле это базовая настройка для всех делегатов/протоколов, и теперь, когда вы нажимаете на свою кнопку, вы можете перенаправить действие на свой контроллер:

 @IBAction myButtonTouchUpInside() {

     self.delegate.buttonTappedForCell(self)
 }

После всего этого действуйте, как в части 1. То есть либо reloadRowsAtIndexPaths, либо пара beginUpdates/endUpdates, как описано выше.

Надеюсь, поможет!

person Jiri Trecak    schedule 22.09.2015
comment
Я добавил решение к другой вашей проблеме, так как наконец понял, что вы на самом деле пытаетесь сделать :) - person Jiri Trecak; 22.09.2015
comment
Привет Иржи! Я понял, что то, что вы объяснили о разнице между reloadRowsAtIndexPaths и парой beginUpdates/endUpdates, имеет решающее значение. На самом деле я пошел дальше и смело отредактировал ваш ответ, чтобы расширить этот момент. Посмотри, понравится ли тебе.... - person Fattie; 22.09.2015
comment
Кстати, я только что обнаружил setNeedsUpdateConstraints. . . интересно, что никто не упомянул об этом в этом контексте. Он выполняет ту же роль, что и 'reloadRowsAtIndexPaths. - person Fattie; 23.09.2015
comment
Вот почему так не должно быть :) vs updateconstra">stackoverflow.com/questions/20609206/ - person Jiri Trecak; 23.09.2015
comment
Иржи, спасибо за огромное количество полезной информации, которая, я уверен, будет невероятно полезна для будущих пользователей Google. Я щелкнул по токену в знак признательности, спасибо! - person Fattie; 23.09.2015

Вы пытались вызвать reloadRowsAtIndexPaths для индекса ячейки? он должен анимироваться до нового размера, если ограничения настроены правильно.

person shaish    schedule 22.09.2015

Вы должны вызывать self.tableView.reloadData() сразу ПОСЛЕ изменения текста метки ячейки.

Это заставит tableView перерисовать ячейки. Вот что произошло, когда вы прокручиваете, ячейка становится reused и перерисовывается, когда она возвращается снова.

РЕДАКТИРОВАТЬ:

Если вы не можете или не хотите выполнять reloadData в своем tableView, вы можете использовать:

self.tableView.beginUpdates()
self.tableView.reloadRowsAtIndexPaths([NSIndexPath(row:0 section:0)] withRowAnimation:UITableViewRowAnimation.Automatic)
self.tableView.endUpdates()
person Loegic    schedule 22.09.2015
comment
На самом деле, если ваш контент статичен и форма справки похожа на форму справки, простой reloadData() будет работать быстро. - person Loegic; 22.09.2015
comment
@JoeBlow Вовсе нет, reloadRowsAtIndexPaths перезагрузит только предоставленный вами массив строк. Если вы предоставите массив только с одной строкой, он перезагрузит только эту. Нет другого способа сделать его более эффективным - person Loegic; 22.09.2015
comment
Кстати, beingUpdates и endUpdates необходимы только в том случае, если вы делаете несколько обновлений, которые хотите выполнять вместе. Если вы делаете одиночные reloadRowsAtIndexPaths, begin/endUpdates не нужны. - person Rob; 22.09.2015
comment
Однако @Rob я понял, что, если я не ошибаюсь, вы действительно можете вызвать пару beginUpdates / endUpdates, если вы только что изменили макет (представьте, что вы утроили размер шрифта в пунктах в блоке текста, чтобы ячейка была теперь гораздо дольше). Напротив, если (как было задано в моем вопросе) вы существенно меняете данные модели (как, как мы теперь знаем, следует), действительно придется перезагружать. Звучит правильно, я думаю........(надеюсь :) ) - person Fattie; 22.09.2015
comment
@JoeBlow - Да, как говорится в документах. Вы также можете использовать [beginUpdates], а затем метод endUpdates, чтобы анимировать изменение высоты строк без перезагрузки ячейки. Но вызывать beginUpdates, потом перезагружать ячейку, а потом звонить endUpdates избыточно. - person Rob; 22.09.2015

Я не знаю ваш код, но действительно ли вы выполняли изменения пользовательского интерфейса в основном потоке. Та же проблема случилась со мной и была решена путем размещения выполнения в основном потоке.

dispatch_async(dispatch_get_main_queue()) { () -> Void in
        [...]
    }
person Philipp Otto    schedule 22.09.2015
comment
Привет, Филипп, это хороший момент, но, боюсь, это не моя конкретная проблема! - person Fattie; 22.09.2015