Пишу сейчас одну программу под iOS, и в ней мне понадобилось в окне настроек уметь прятать и показывать некоторые ячейки с дополнительными опциями. Настройки, понятное дело, сделаны на статической UITableView
. Если просто спрятать ячейку, то она конечно спрячется, но занимаемое ею место продолжит занимать. Нужно что-то другое.
Поиск решения в интренете принес некоторые плоды, но ничего действительно работающего. Так что пришлось засучить рукава и покопаться самому. В результате все оказалось достаточно просто, если аккуратно обойти некоторые подводные камни. Результатом своих экспериментов я решил поделиться с общественностью; тема в интернете вроде бы таки да раскрыта не полностью. Так что кому надо — вот:
Нюансы под катом.
Для динамического изменения высоты ячеек служит метод делегата tableView:heightForRowAtIndexPath:
, это всем известно. Но вот что в документации не упомянуто, так это то, что изменение высоты ячеек анимируется — то, что доктор прописал. В вышеупомянутом методе на основании некоторого состояния возвращаем высоту интересующих нас ячеек (в самом простом случае — 0 или нормальную высоту ячейки), а когда нужно ячейки спрятать/показать — меняем состояние и инициируем обновление ячеек. Как инициируем? А вот так:
1 2 3 4 |
tableView.beginUpdates() tableView.endUpdates() |
А теперь гвоздь программы, нюанс номер один. tableView:heightForRowAtIndexPath:
получает на вход indexPath
ячейки, для которой нужно определить высоту. Логично. Что менее логично, так это то, что вызов tableView.cellForRowAtIndexPath(indexPath)
приводит к бесконечной рекурсии и, соотвественно, крэш тебе в бубен, мой дорогой программист. Ну ничего. Нормальные герои всегда идут в обход, проживем без tableView.cellForRowAtIndexPath:
.
Первый вариант, который просится в голову, подойдет для не очень ленивых программистов: заложиться на конкретные значения indexPath.row
. Но я вот, например, слишком ленив для такого решения и не имею никакого желания править баги, которые неизбежно возникнут в дальнейшем при изменении таблицы, после чего недостаточно ленивый программист должен будет все номера строк вручную менять на новые. Мы, истинно ленивые программисты, однозначно пойдем другим путем:
- все динамические ячейки оформляются как outlets;
- переопределятся
tableView:cellForRowAtIndexPath:
; - для каждой ячейки динамической ячейки, возвращаемой из этого метода, сохраняется в сторонку ее индекс;
- в
tableView:heightForRowAtIndexPath:
для определения нужных мне ячеек используются сохраненные ранее индексы.
Итого, получается что-то в таком вот роде:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
@IBOutlet weak var dynamicCell: UITableViewCell! var hiddenCellsAreShown: Bool = false var dynamicCellIndexPath: NSIndexPath? override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let result = super.tableView(tableView, cellForRowAtIndexPath: indexPath) if result === hiddenCell1 { dynamicCellIndexPath = indexPath } return result } override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { if !hiddenCellsAreShown && indexPath == dynamicCellIndexPath { return 0 } return super.tableView(tableView, heightForRowAtIndexPath: indexPath) } // где-то в нужный момент hiddenCellsAreShown = determineIfDynamicCellsHaveToBeShown() tableView.beginUpdates() tableView.endUpdates() |
Но на этом нюансы не заканчиваются.
Обратите внимание, что динамические ячейки нигде не прячутся. Во-первых, тот же видимый эффект можно получить, установив для ячеек опцию Clip Subviews в Xcode (ну или программно). В этом случае высота равная нулю гарантирует, что ничего отображаться не будет. Во-вторых, представьте, что ячейка должна быть спрятана. Свойство hidden нужно ставить в true после анимации спрятывания, правильно? Ну а когда она закончится никто не знает. Нет в API никаких коллбэков по этому поводу; так что проще всего и не заморачиваться.
Также вы непременно заметите, что спрятывание стандартных ячеек (Basic, Detail, Subtitle) анимируется криво. Вначале текст сплющивается в ноль, и потом это безобразие ездит по экрану. Непорядок. Так что нужно использовать Custom ячейки. Благо, в этом ничего сложного нет.
Ну и наконец, Auto Layout. Вы же именно его будете использовать в Custom ячейках, вероятность 99.99%. Тогда учтите, что Auto Layout очень, очень не любит нулевые размеры. Если вы хотите получить эффект «сплющивания» содержимого, сплющивайте не в 0, а в 1 point например. Такого рода условие легко задать с помощью правила-неравенства. Ну а проще всего будет просто выровнять все контролы в ячейке по центру в вертикальной оси. В примере так и сделано, и для меток (labels) это выглядит естественно.
На этом, по счастью, нюансы вроде бы заканчиваются. А если вдруг найдете еще — дайте знать, мне тоже интересно!