iOS – CoreDataにおける検索条件のエスケープ

CoreDataで以下のコードでtargetNameに該当するデータを検索する場合、targetNameにシングルクォート「'」が含まれていると、シングルクォートの「囲み」の整合性が取れなくなるので、アプリが落ちます。

NSString *searchCondition = [NSString stringWithFormat:@"name = '%@'", targetName];
NSPredicate *predicate = [NSPredicate predicateWithFormat:searchCondition];
[fetchRequest setPredicate:predicate];

このような場合はtargetNameに含まれているシングルクォートを以下のようにして事前にエスケープしておけば大丈夫です。

targetName = [targetName stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"]

 

 

iOS – UITableViewで論理削除をする実行する例

UITableViewにおける削除UITableViewにはデフォルトでデータの編集機能が備わっており、CoreDateフレームワークと組み合わせて使用するときはNSFetchedResultsControllerDelegateプロトコルを当該ViewControllerに採用して制御するのが一般的です。

データを物理削除する場合は他のサイトでいろいろと紹介されていますが、ここではデータを論理削除する場合の例をメモとして残しておきます。

 

論理削除処理をどこに書くか
UITableViewの「編集」ボタンをタップして削除したいデータを選択し、「削除」ボタンをタップすると(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPathメソッドが呼び出されます。そのため、この中に論理削除の処理を書けばよいわけです。以下がそのサンプルコードです。

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
        Unit *unit = [self.fetchedResultsController objectAtIndexPath:indexPath];
        unit.del_flg = @"1";

        NSError *error = nil;
        if (![context save:&error]) {
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            return;
        }
    }
}

上記のコードではUnitというManagedObjectのdel_flg属性を"1"を設定することで論理削除を行なっています。ポイントはユーザによって削除処理が行わたかどうかをeditingStyle変数を調べて判断しているところです。

 

削除されたデータを非表示にするにはどうするか
上記のコードでデータ自体は論理削除したのですが、それを画面に反映させる必要があります。ManagedObjectに変更があるとNSFetchedResultsControllerDelegateプロトコルのデリゲートメソッドである(void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType (NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPathが呼び出されます。これを利用しましょう。以下がよく見られる実装例です。

- (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:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

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

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

上記コードの13行目がポイントです。テーブルビューに対してdeleteRowsAtIndexPathsメソッドを実行し、テーブルビューの見た目から削除対象の行を削除しています。

 

あ、あとNSFetchedResultsControllerのインスタンス作成時に論理削除されたものを抽出しないように検索条件を設定するのを忘れずに。一応、サンプルコードを載せておきます。

- (NSFetchedResultsController *)fetchedResultsController
{
    if (_fetchedResultsController != nil) {
        return _fetchedResultsController;
    }

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Unit" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];

    [fetchRequest setFetchBatchSize:20];

    // 検索条件を指定。削除フラグがたっていないもののみ取得する
    NSString *searchCondition = [NSString stringWithFormat:@"del_flg MATCHES '0'"];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:searchCondition];
    [fetchRequest setPredicate:predicate];

    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:NO];
    NSArray *sortDescriptors = @[sortDescriptor];
    [fetchRequest setSortDescriptors:sortDescriptors];

    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
    aFetchedResultsController.delegate = self;
    self.fetchedResultsController = aFetchedResultsController;

	NSError *error = nil;
	if (![self.fetchedResultsController performFetch:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
	    return  nil;
	}

    return _fetchedResultsController;
}

 

以上になります。

 

環境

XCode 4.6