Почему ячейки таблицы просмотра исчезают при перезагрузке с помощью reloadRowsAtIndexPaths?
Здесь у меня есть пример с образцом:
http://dl.dropbox.com/u/7834263/ExpandingCells.zip
В этом проекте UITableView имеет пользовательский UITableViewCell. В каждой ячейке находятся 3 UIViews, содержащие метку.
Цель состоит в том, чтобы развернуть ячейку при нажатии, а затем свернуть ее при повторном нажатии. Сама ячейка должна изменить ее высоту, чтобы открыть подзоны. Вставка или удаление строк неприемлема.
Демо-проект работает почти так, как ожидалось. Фактически, в iOS 4.3 он работает отлично. Однако в iOS 5, когда строки рушится, предыдущие ячейки волшебным образом исчезают.
Чтобы повторно создать проблему, запустите проект в симуляторе или устройстве с помощью iOS 5 и коснитесь первой ячейки, чтобы развернуть ее. Затем снова коснитесь ячейки, чтобы свернуть ее. Наконец, коснитесь ячейки непосредственно под ней. Предыдущее исчезает.
Продолжение прослушивания для каждой ячейки в секции приведет к исчезновению всех ячеек, где отсутствует весь раздел.
Ive также попытался использовать reloadData вместо текущей настройки, но это разрушает анимацию и в любом случае немного похож на хак. reloadRowsAtIndexPaths должен работать, но вопрос в том, почему это не так?
Смотрите изображения того, что происходит ниже:
Появится таблица:
![table appears]()
Ячейка расширяется:
![table expands]()
Клетка разрушается:
![table collapses]()
Ячейка исчезает (при нажатии на ячейку внизу):
![cell disappears]()
Продолжайте повторять, пока не исчезнет весь раздел:
![section disappears]()
EDIT:
Переопределение альфы - это взлом, но работает. Вот еще один "взлом", который также исправляет его, но ПОЧЕМУ он его исправляет?
JVViewController.m строка 125:
if( previousIndexPath_ != nil )
{
if( [previousIndexPath_ compare:indexPath] == NSOrderedSame ) currentCellSameAsPreviousCell = YES;
JVCell *previousCell = (JVCell*)[self cellForIndexPath:previousIndexPath_];
BOOL expanded = [previousCell expanded];
if( expanded )
{
[previousCell setExpanded:NO];
[indicesToReload addObject:[previousIndexPath_ copy]];
}
else if( currentCellSameAsPreviousCell )
{
[previousCell setExpanded:YES];
[indicesToReload addObject:[previousIndexPath_ copy]];
}
//[indicesToReload addObject:[previousIndexPath_ copy]];
}
ИЗМЕНИТЬ 2:
Сделал несколько незначительных изменений в демонстрационном проекте, стоит проверить и рассмотреть метод JVViewController didSelectRowAtIndexPath.
Ответы
Ответ 1
Ваша проблема в setExpanded: в JVCell.m вы непосредственно редактируете кадр целевой ячейки в этом методе.
- (void)setExpanded:(BOOL)expanded
{
expanded_ = expanded;
CGFloat newHeight = heightCollapsed_;
if( expanded_ ) newHeight = heightExpanded_;
CGRect frame = self.frame;
frame.size.height = newHeight;
self.frame = frame;
}
Обновить его до:
- (void)setExpanded:(BOOL)expanded
{
expanded_ = expanded;
}
Затем удалите вызов -reloadRowsAtIndexPaths:withRowAnimation:
в строке 163 JVViewController.m и он будет оживлять, как ожидалось.
-reloadRowsAtIndexPaths:withRowAnimation:
ожидает, что для предоставленных indexPaths будут возвращены разные ячейки. Поскольку вы настраиваете размеры -beginUpdates
и -endUpdates
, достаточно, чтобы снова отобразить ячейки таблицы.
Ответ 2
Возможно, мне не хватает точки, но почему вы просто не используете:
UITableViewRowAnimationNone
Я имею в виду вместо:
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];
использовать
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationNone];
Ответ 3
Чтобы оживить изменения высоты таблицы, просто вызовите.
[tableView beginUpdates];
[tableView endUpdates];
Не вызывайте reloadRowsAtIndexPaths:
См. Можете ли вы изменить изменение высоты в UITableViewCell при выборе?
Ответ 4
Ячейка, которая исчезает, является предыдущей ячейкой, которая не меняет размер. Как указано в документации reloadRowsAtIndexPaths:withRowAnimation:
:
Таблица оживляет новую ячейку, поскольку она оживляет старую строку.
Что происходит, так это непрозрачность, равная 1, а затем сразу же устанавливается на 0 и поэтому исчезает.
Если и предыдущий, и новый размер изменения ячейки, он работает по назначению. Это связано с тем, что обновления "Начало/конец" уведомляют об изменениях высоты и создают новые анимации на этих ячейках, переопределяя теги reloadRowsAtIndexPaths:withRowAnimation:
.
Ваша проблема связана с злоупотреблением reloadRowsAtIndexPaths:withRowAnimation:
для изменения размера ячеек, когда он предназначен для загрузки новых ячеек.
Но вам не нужно reloadRowsAtIndexPaths:withRowAnimation:
вообще. Просто измените расширенное состояние ячеек и сделайте обновления начала/конца. Это будет обрабатывать всю анимацию для вас.
В качестве побочной заметки я обнаружил, что синяя селекция немного раздражает, в JVCell устанавливают selectedBackgroundView на то же изображение, что и backgroundView (или создают новое изображение, которое имеет правильный вид выбранной ячейки).
EDIT:
Переместите оператор, добавив previousIndexPath_
в indicesToReload
в оператор if (в строке 132), чтобы он был добавлен только в том случае, если предыдущая ячейка была расширена и ее необходимо изменить.
if( expanded ) {
[previousCell setExpanded:NO];
[indicesToReload addObject:[previousIndexPath_ copy]];
}
Это удалит случай, когда предыдущая свернутая ячейка исчезнет.
Другим вариантом было бы установить previousIndexPath_
на nil, когда текущая ячейка будет свернута и только установить ее, когда ячейка будет расширяться.
Это все еще похоже на хак. Выполнение как reloadRows, так и начального/конечного обновлений заставляет tableView перезагружать все дважды, но оба они кажутся необходимыми для правильной анимации. Я полагаю, что если таблица не слишком велика, это не будет проблемой производительности.
Ответ 5
Короткий, прагматичный ответ: изменение UITableViewRowAnimationAutomatic
до UITableViewRowAnimationTop
решает проблему. Больше никаких исчезающих рядов! (тестируется на iOS 5.1)
Другой короткий, прагматичный ответ, так как UITableViewRowAnimationTop
, как говорят, вызывает свои проблемы. Создайте новый вид ячейки вместо изменения существующего одного кадра. В реальном приложении данные, отображаемые в представлении ячейки, как предполагается, будут в части модели приложения, поэтому, если правильно спроектировать, не должно возникнуть проблемы с созданием другого представления ячейки, которое отображает одни и те же данные только по-другому (рамка в нашем случае).
Еще несколько соображений относительно оживления перезагрузки одной и той же ячейки:
UITableViewRowAnimationAutomatic
, кажется, разрешает UITableViewRowAnimationFade
в некоторых случаях, когда вы видите, что ячейки исчезают и исчезают. Предполагается, что новая ячейка исчезнет, в то время как старый погаснет. Но здесь старая ячейка и новая - одно и то же. Итак, может ли это работать? На уровне Core Animation: возможно ли постепенное исчезновение представления и его постепенное исчезновение? Звучит сомнительно. Таким образом, вы видите, что вы просто исчезаете. Это можно было бы считать ошибкой Apple, поскольку ожидаемое поведение может заключаться в том, что если одно и то же представление изменилось, свойство alpha не будет анимировано (так как оно не может одновременно анимировать 0 и 1 одновременно), но вместо этого будет только анимированный кадр, цвет и т.д.
Обратите внимание, что проблема заключается только в отображении анимации - если вы прокручиваете прочь и обратно, все будет выглядеть правильно.
В iOS 4.3 режим Automatic
может быть разрешен для чего-то другого, кроме Fade
, поэтому все работает там (как вы их пишете) - я не вникал в это.
Я не знаю, почему iOS выбирает режим Fade
, когда он это делает. Но один из случаев, когда это происходит, - это когда ваш код запрашивает перезагрузку ранее использованной ячейки, которая сбрасывается, и отличается от текущей ячейки. Обратите внимание, что ранее перехваченная ячейка всегда перезагружается, эта строка в вашем коде всегда называется:
[indicesToReload addObject:[previousIndexPath_ copy]];
Это объясняет описанный вами сценарий исчезновения скрытых объектов.
Кстати, beginUpdates/endUpdates кажутся мне взломанным. Предполагается, что эта пара вызовов содержит анимации, и нет никаких анимаций, которые вы добавляете в дополнение к строкам, которые вы уже просили перезагрузить. Все, что он делал в этом случае, в магическом случае приводит к тому, что режим Automatic
не выбирает Fade
в некоторых случаях - но это просто затмило проблему.
Последнее замечание: я играл в режиме Top
и обнаружил, что он также может вызвать проблемы. Например, включение следующего кода приводит к тому, что ячейки исчезают funkily:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationTop];
}
Не уверен, что здесь есть реальная проблема (аналогичная той, которая затухает в одном и том же виде одновременно) или, может быть, ошибка Apple.
Ответ 6
Я только что загрузил ваш проект и нашел этот раздел кода в делегате didSelectRowAtIndexPath
, где используется reloadRowsAtIndexPaths
.
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView beginUpdates];
[tableView endUpdates];
вместо вышеизложенного, почему бы вам не попробовать это?
[tableView beginUpdates];
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView endUpdates];
Я предполагаю, что я считаю, что reloadRowsAtIndexPaths:...
работает только при завершении между вызовами:
- (void)beginUpdates;
- (void)endUpdates;
Кроме того, поведение undefined и, как вы обнаружили, довольно ненадежное. Цитирование соответствующей части "" Руководство по программированию таблиц для iPhone OS":
Чтобы анимировать пакетную вставку и удаление строк и разделов, вызовите методы вставки и удаления внутри блока анимации, определенные последовательными вызовами beginUpdates и endUpdates. Если вы не вызываете методы вставки и удаления в этом блоке, индексы строк и секций могут быть недействительными. beginUpdates... endUpdates блоки не являются вложенными.
В конце блока, то есть после возврата endUpdates - представление таблицы запрашивает свой источник данных и делегирует, как обычно, данные строки и раздела. Таким образом, объекты коллекции, поддерживающие представление таблицы, должны обновляться, чтобы отражать новые или удаленные строки или разделы.
ReloadSections: withRowAnimation: и reloadRowsAtIndexPaths: withRowAnimation: методы, которые были представлены в iPhone OS 3.0, связаны с описанными выше методами. Они позволяют запрашивать представление таблицы для перезагрузки данных для определенных разделов и строк вместо загрузки всего видимого вида таблицы путем вызова reloadData.
Там может быть и другая веская причина, но позвольте мне немного подумать об этом, так как у меня тоже есть код, я мог бы с этим справиться. Надеюсь, мы должны это выяснить...
Ответ 7
Когда вы используете этот метод, вы должны быть уверены, что находитесь в основном потоке.
Обновление UITableViewCell следующим образом должно сделать трюк:
- (void) refreshTableViewCell:(NSNumber *)row
{
if (![[NSThread currentThread] isMainThread])
{
[self performSelector:_cmd onThread:[NSThread mainThread] withObject:row waitUntilDone:NO];
return;
}
/*Refresh your cell here
...
*/
}
Ответ 8
@Javy, я заметил странное поведение во время тестирования вашего приложения.
При работе на симуляторе iPhone 5.0 предыдущая переменнаяIndexpth_ имеет
class NSArray
(похоже.) здесь вывод отладчика
(lldb) po previousIndexPath_
(NSIndexPath *) $5 = 0x06a67bf0 <__NSArrayI 0x6a67bf0>(
<JVSectionData: 0x6a61230>,
<JVSectionData: 0x6a64920>,
<JVSectionData: 0x6a66260>
)
(lldb) po [previousIndexPath_ class]
(id) $7 = 0x0145cb64 __NSArrayI
Тогда как в симуляторе iPhone 4.3 он имеет тип NSIndexPath
.
lldb) po [previousIndexPath_ class]
(id) $5 = 0x009605c8 NSIndexPath
(lldb) po previousIndexPath_
(NSIndexPath *) $6 = 0x04b5a570 <NSIndexPath 0x4b5a570> 2 indexes [0, 0]
Вам известна эта проблема? Не уверен, поможет ли это, но подумал дать вам знать.
Ответ 9
Я думаю, что вы не должны сохранять предыдущий индекс, когда ячейка не расширяется,
попробуйте изменить, вы выбрали метод, как показано ниже, его работа прекрасна.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
BOOL currentCellSameAsPreviousCell = NO;
NSMutableArray *indicesToReload = [NSMutableArray array];
if(previousIndexPath_ != nil)
{
if( [previousIndexPath_ compare:indexPath] == NSOrderedSame ) currentCellSameAsPreviousCell = YES;
JVCell *previousCell = (JVCell*)[self cellForIndexPath:previousIndexPath_];
BOOL expanded = [previousCell expanded];
if(expanded)
{
[previousCell setExpanded:NO];
}
else if (currentCellSameAsPreviousCell)
{
[previousCell setExpanded:YES];
}
[indicesToReload addObject:[previousIndexPath_ copy]];
if (expanded)
previousIndexPath_ = nil;
else
previousIndexPath_ = [indexPath copy];
}
if(currentCellSameAsPreviousCell == NO)
{
JVCell *currentCell = (JVCell*)[self cellForIndexPath:indexPath];
BOOL expanded = [currentCell expanded];
if(expanded)
{
[currentCell setExpanded:NO];
previousIndexPath_ = nil;
}
else
{
[currentCell setExpanded:YES];
previousIndexPath_ = [indexPath copy];
}
// moving this line to inside the if statement blocks above instead of outside the loop works, but why?
[indicesToReload addObject:[indexPath copy]];
}
// commenting out this line makes the animations work, but the table view background is visible between the cells
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];
// using reloadData completely ruins the animations
[tableView beginUpdates];
[tableView endUpdates];
}
Ответ 10
Эта проблема вызвана возвратом кэшированных ячеек в cellForRowAtIndexPath.
ReloadRowsAtIndexPaths ожидает получить новые новые ячейки от cellForRowAtIndexPath.
Если вы сделаете это, вы будете в порядке... никаких обходных решений не требуется.
Из документа Apple:
"Перезагрузка строки заставляет представление таблицы запрашивать свой источник данных для новой ячейки для этой строки".
Ответ 11
У меня была аналогичная проблема, когда я хотел развернуть ячейку, когда переключатель активирован, чтобы отобразить, и дополнительную метку и кнопку в ячейке, которая обычно скрыта, когда ячейка имеет высоту по умолчанию (44). Я пробовал разные версии reloadRowsAtPath безрезультатно. Наконец, я решил сохранить его проще, добавив условие на heightForRowAtIndexPath так:
override func tableView(tableView: UITableView,heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if ( indexPath.row == 2){
resetIndexPath.append(indexPath)
if resetPassword.on {
// whatever height you want to set the row to
return 125
}
}
return 44
}
И в любом коде, который вы хотите вызвать расширение ячейки, просто вставьте tableview.reloadData(). В моем случае это было, когда переключатель был включен, чтобы указать желание reset пароля.
@IBAction func resetPasswordSwitch(sender: AnyObject) {
tableView.reloadData()
}
С этим подходом нет отставания, нет видимого способа увидеть, что таблица перезагружена, а расширение продажи происходит постепенно, как и следовало ожидать. Надеюсь, это поможет кому-то.
Ответ 12
Попробуйте надеяться, что это поможет u Расширение ячеек