古いスタイルの削除と挿入の代わりに、iOS 5の気の利いた行移動呼び出しを使用して、テーブルビューをアニメーション化してモデルの状態の変更に一致させたいと思います。
変更には、並べ替えとインプレース更新の両方が含まれる場合があり、両方をアニメーション化する必要があるため、一部の行にはが必要になりますreloadRowsAtIndexPaths
。
だが!UITableView
更新されたセルが移動のために位置をシフトした場合、移動が存在する場合の行のリロードの処理は明らかに間違っているように見えます。古いdelete+insert呼び出しを同等の方法で使用すると、正常に機能します。
ここにいくつかのコードがあります。冗長であることをお詫びしますが、コンパイルして実行します。肉はdoMoves:
メソッドにあります。以下の解説。
#define THISWORKS
@implementation ScrambledList // extends UITableViewController
{
NSMutableArray *model;
}
- (void)viewDidLoad
{
[super viewDidLoad];
model = [NSMutableArray arrayWithObjects:
@"zero",
@"one",
@"two",
@"three",
@"four",
nil];
[self.navigationItem setRightBarButtonItem:[[UIBarButtonItem alloc] initWithTitle:
#ifdef THISWORKS
@"\U0001F603"
#else
@"\U0001F4A9"
#endif
style:UIBarButtonItemStylePlain
target:self
action:@selector(doMoves:)]];
}
-(IBAction)doMoves:(id)sender
{
int fromrow = 4, torow = 0, changedrow = 2; // 2 = its "before" position, just like the docs say.
// some model changes happen...
[model replaceObjectAtIndex:changedrow
withObject:[[model objectAtIndex:changedrow] stringByAppendingString:@"\u2032"]];
id tmp = [model objectAtIndex:fromrow];
[model removeObjectAtIndex:fromrow];
[model insertObject:tmp atIndex:torow];
// then we tell the table view what they were
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:changedrow inSection:0]]
withRowAnimation:UITableViewRowAnimationRight]; // again, index for the "before" state; the tableview should figure out it really wants row 3 when the time comes
#ifdef THISWORKS
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:fromrow inSection:0]]
withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:torow inSection:0]]
withRowAnimation:UITableViewRowAnimationAutomatic];
#else // but this doesn't
[self.tableView moveRowAtIndexPath:[NSIndexPath indexPathForRow:fromrow inSection:0]
toIndexPath:[NSIndexPath indexPathForRow:torow inSection:0]];
#endif
[self.tableView endUpdates];
}
#pragma mark - Table view data source boilerplate, not very interesting
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return model.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@""];
if (cell == nil)
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@""];
[cell.textLabel setText:[[model objectAtIndex:indexPath.row] description]];
[cell.detailTextLabel setText:[NSString stringWithFormat:@"this cell was provided for row %d", indexPath.row]];
return cell;
}
コードの機能:小さなモデル(小さな可変配列)を設定します。ボタンを押すと、リストの中央の要素に小さな変更が加えられ、最後の要素が最初の要素に移動します。次に、これらの変更を反映するようにテーブルビューを更新します。中央の行を再読み込みし、最後の行を削除して、新しい行のゼロを挿入します。
これは機能します。実際、ログをに追加するとcellForRowAtIndexPath
、行2の再読み込みを要求しますが、実際に更新を行うときに挿入されるため、テーブルビューは行3を正しく要求します。ハザ!
次に、上部の#ifdefをコメントアウトして、代わりにmoveRowAtIndexPath呼び出しを使用します。
これで、テーブルビューは行2を削除し、新しい行2を要求し(間違っています!)、最後の行2の位置に挿入します(これも間違っています!)。最終的な結果として、行1が1つではなく2つのスロットに移動し、画面外にスクロールして強制的にリロードすると、モデルとの同期がどのようにずれているかがわかります。moveRowAtIndexPath
テーブルビューのプライベートモデルを別の順序で変更し、リロードまたはモデルフェッチで「古い」インデックスパスの代わりに「新しい」インデックスパスを使用する必要があるかどうかは理解できましたが、それは起こっていません。2番目の「後」の写真では、3番目と4番目の行が逆の順序になっていることに注意してください。これは、リロードするセルに関係なく発生しないはずです。
私の語彙は、Appleを罵倒するカラフルなものになりました。代わりに自分を罵倒する必要がありますか?行の移動は、同じ更新ブロックでの行の再読み込みとはまったく互換性がありませんか(また、私は、挿入と削除を疑っています)?バグレポートを提出する前に、誰かに教えてもらえますか?