Core Dataで管理されているオブジェクトを、UITableViewでの順序入れ替えに対応させる方法例

【前提条件】

  • あらかじめ、対象のManagedObjectに「position」というプロパティを作っておきます。
  • UITableViewの中身はNSFetchedResultsControllerで管理しておきます。
  • NSFetchedResultsControllerに渡すFetchRequestに、「position」でソートするようSortDescriptorを設定しておきます。
    // Edit the sort key as appropriate.
        NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"position" ascending:YES];
        NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];

セルが移動された際、以下のようなコードでpositionの値を書き換えることで、入れ替えが実現できました。
クラス名は、対象にするモデルクラスに書き換えてください。

// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
    NSManagedObjectContext *context = [[MRAppDelegate sharedDelegate] managedObjectContext];
    MyObject* targetObject = (MyObject *)[self.fetchedResultsController objectAtIndexPath:fromIndexPath];
    MyObject* previousObject = nil;
    MyObject* nextObject = nil;
    id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:fromIndexPath.section];

    if (fromIndexPath.row < toIndexPath.row) {

        previousObject = (MyObject *)[self.fetchedResultsController objectAtIndexPath:toIndexPath];

        if ([sectionInfo numberOfObjects] - 1 != toIndexPath.row) {
            nextObject = (MyObject *)[self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:toIndexPath.row + 1 inSection:toIndexPath.section]];
        }

    } else {

        if (toIndexPath.row != 0) {
            previousObject = (MyObject *)[self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:toIndexPath.row - 1 inSection:toIndexPath.section]];
        }

        nextObject = (MyObject *)[self.fetchedResultsController objectAtIndexPath:toIndexPath];

    }

    float sakura = 1;
    float aya = 0;

    if (nextObject) {
        if (previousObject) {
            sakura = (nextObject.position - previousObject.position) / 2;
        } else {
            aya = nextObject.position - sakura * 2;
        }
    }

    if (previousObject) {
        aya = previousObject.position;
    }

    targetObject.position = sakura + aya;

    [context save:nil];

}

ただ、これだけだとTableViewに更新が行き渡らないので、
以下のように、controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:
にてフックして、reloadDataをするとうまくいきます。

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath
{
    UITableView *tableView = self.tableView;

    switch(type)
    {

        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeUpdate:
            [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
            break;

        case NSFetchedResultsChangeMove:
            [tableView reloadData];
            break;
    }
}

「position」というプロパティを作らず、ArrangedなRelationshipを定義して、それを使ってソートする方法もありますが、
iOS 4が対応していないため今回は使っていません。

Ruby on RailsによるWEBシステム開発、Android/iPhoneアプリ開発、電子書籍配信のことならお任せください この記事を書いた人と働こう! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

週刊Railsウォッチ

インフラ

BigBinary記事より

ActiveSupport探訪シリーズ