ちょっとだけ本格的なiPhone Core Dataアプリケーションへの拡張

Konton's iPhone application labolatory

English top page

関連を使った拡張を行う

以前の記事「初めてのCore Data on iPhone」でCore Dataの超簡易サンプルとして紹介した「FirstCoreData」ですが、前回の説明では関連について全く触れていませんでした。 そこで今回は前回のサンプルに手を加えて、関連を使用した発展バージョンにしてみます。ちなみにこれもiPhone SDK 3.1.2用です。 残念ながら現在のXcodeでは操作方法が異なる部分があるため、そのまま応用することはできません。


基本方針

まず、基本的な設計についてですが、関連を使うために2つのエンティティからなるデータベースにします。親オブジェクトの中に子オブジェクトが複数格納されるスタイルです。 とりあえず今回はそれぞれのオブジェクトに名前を付けられるようにし、それをナビゲーションバー付きのテーブルビューで行ったり来たりできるようにします。 名前はテキストフィールドのあるビューを使って後からでも編集できるようにします。親オブジェクトのリストも子オブジェクトのリストもどちらも名前で自動的にソートされるようにします。


下ごしらえ

まずは下ごしらえとして、データモデルの変更と、それぞれのエンティティに対応するNSManagedObjectを継承したクラスを作成します。


データモデルの変更

データモデルは前回と同じ「FirstCoreData.xcdatamodel」ファイルを編集します。 今あるエンティティは消し、新たに2個のエンティティ「RootLevel」および「SecondLevel」を作ります。それぞれ属性としてデータ型が文字列の「name」を持たせます。 次に関連として「RootLevel」に「secondLevels」、「SecondLevel」に「rootLevel」を追加します。 「secondLevels」のデスティネーションを「SecondLevel」にし、逆には「rootLevel」を指定します。そして対多関連にチェックを入れます。


対応するNSManagedObjectクラスの作成

次にNSManagedObjectの継承クラスを2つ作るのですが、これはXcodeでそのまますんなりとは作れません。 そのため、まずはNSObjectの継承クラスとして新規作成し、あとから親クラスをNSManagedObjectに変更します。それぞれのヘッダファイルに属性と関連のプロパティを設定します。 RootLevelにはsecondLevels操作用のメソッド名を追加する必要がありますが、これには先ほど作成したxcdatamodelファイルを使うと楽です。 方眼紙のような表示の部分でsecondLevelsをコントロールキーを押しつつクリック(2ボタンマウスならキーボードは使わずに右クリック)し、 「Obj-C 2.0 Method declarations をクリップボードにコピー」を選んで、「CoreDataGeneratedAccessors」というカテゴリのインターフェースを追加します。 具体的なコードは以下のようになります。

//
// RootLevel.h
// FirstCoreData
//
// Created by Konton on 09/11/06.
// Copyright 2009 Konton. All rights reserved.
//


@interface RootLevel : NSManagedObject {
}

@property (nonatomic,retain) NSString *name;
@property (nonatomic,retain) NSSet *secondLevels;

@end

@interface RootLevel (CoreDataGeneratedAccessors)

- (void)addSecondLevelsObject:(NSManagedObject *)value;
- (void)removeSecondLevelsObject:(NSManagedObject *)value;
- (void)addSecondLevels:(NSSet *)value;
- (void)removeSecondLevels:(NSSet *)value;

@end

下のレベルのヘッダーは以下のようになります。

//
// SecondLevel.h
// FirstCoreData
//
// Created by Konton on 09/11/06.
// Copyright 2009 Konton. All rights reserved.
//


@class RootLevel;

@interface SecondLevel : NSManagedObject {
}

@property (nonatomic,retain) NSString *name;
@property (nonatomic,retain) RootLevel *rootLevel;

@end

メソッドファイルは属性や関連の名前を全部「@dynamic」マクロで記載しておきます。実際のコードは以下のようになります。

//
// RootLevel.m
// FirstCoreData
//
// Created by Konton on 09/11/06.
// Copyright 2009 Konton. All rights reserved.
//

#import "RootLevel.h"


@implementation RootLevel

@dynamic name;
@dynamic secondLevels;

@end

下のレベルのメソッドファイルも同様に以下のようになります。

//
// SecondLevel.m
// FirstCoreData
//
// Created by Konton on 09/11/06.
// Copyright 2009 Konton. All rights reserved.
//

#import "SecondLevel.h"


@implementation SecondLevel

@dynamic name;
@dynamic rootLevel;

@end

名前入力用ビューコントローラーの作成

続いてオブジェクトの新規作成や編集に使うビューのコントローラーを作ります。ここでは名前を「EditNameViewController」としたとして説明を続けます。 変数としてはテキストフィールドと編集中のオブジェクトを指すポインタ、それと新規作成か既存のオブジェクトかを示すBOOL値のフラグを作ります。 ヘッダファイルはこのような感じになります。

//
// EditNameViewController.h
// FirstCoreData
//
// Created by Konton on 09/11/06.
// Copyright 2009 Konton. All rights reserved.
//

#import <UIKit/UIKit.h>


@interface EditNameViewController : UIViewController {
    UITextField *textField;
    NSManagedObject *editingObject;
    BOOL newObject;
}

@property BOOL newObject;
@property (nonatomic,assign) NSManagedObject *editingObject;
@property (nonatomic,retain) UITextField *textField;

- (void)cancel:(id)sender;
- (void)save:(id)sender;

@end

Core Dataの場合だと、新規オブジェクトは作成した時点で既にコンテキストに挿入した状態で作られるので、セーブだと保存するだけですが、 キャンセルだとコンテキストからの削除が必要になります。 この辺りは配列やディクショナリーなどでのデータ構築でのキャンセル(addObject:やsetObject:forKey:をしないで戻るだけ等)とは発想が違うので注意が必要でしょう。

//
// EditNameViewController.m
// FirstCoreData
//
// Created by Konton on 09/11/06.
// Copyright 2009 Konton. All rights reserved.
//

#import "EditNameViewController.h"
#import "FirstCoreDataAppDelegate.h"
#import "RootViewController.h"
#import "SecondLevelViewController.h"
#import "RootLevel.h"

@implementation EditNameViewController

@synthesize textField;
@synthesize editingObject;
@synthesize newObject;

- (void)dealloc {
    // editingObjectはretainしていないのでreleaseしません。
    [textField release];
    [super dealloc];
}

// このビューはInterfaceBuilderは使わずに構築しています。
- (id)init {
    if (self = [super init]) {
        self.view.backgroundColor = [UIColor groupTableViewBackgroundColor];
        self.textField = [[UITextField alloc]initWithFrame:CGRectMake(20, 84, 280, 31)];
        textField.borderStyle = UITextBorderStyleRoundedRect;
        [self.view addSubview:textField];
        // キャンセルボタンを追加します
        self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc]
            initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
            target:self action:@selector(cancel:)] autorelease];
        // セーブボタンを追加します。
        self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]
            initWithBarButtonSystemItem:UIBarButtonSystemItemSave
            target:self action:@selector(save:)] autorelease];
    }
    return self;
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    textField.text = [editingObject valueForKey:@"name"];
    [textField becomeFirstResponder];
}

- (void)cancel:(id)sender {
    if(newObject) {
        // 新規オブジェクトのキャンセルなので、呼び出し元で挿入したオブジェクトを削除します。
        NSManagedObjectContext *context = editingObject.managedObjectContext;
        [context deleteObject:editingObject];
        NSError *error = nil;
        if (![context save:&error]) {
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }
    }
    [self.navigationController dismissModalViewControllerAnimated:YES];
}

- (void)save:(id)sender {
    // テキストフィールドの内容をキー"name"にセットして変更を保存します。
    [editingObject setValue:textField.text forKey:@"name"];
    if(newObject&&[editingObject.entity.name isEqualToString:@"SecondLevel"]) {
        // SecondLevelでは新規作成のオブジェクトを上位のRootLevelと関連をさせる必要があります。
        FirstCoreDataAppDelegate *appDelegate =
            (FirstCoreDataAppDelegate *)[[UIApplication sharedApplication] delegate];
        // このビューの呼び出し元はアプリケーションデリゲートで作ったナビゲーションコントローラーで
        // 現在一番上に表示されています。
        SecondLevelViewController *controller =
            (SecondLevelViewController *)[appDelegate.navigationController topViewController];
        //呼び出し元のRootLevelのsecondLevelsにeditingObjectを追加します。
        [controller.rootLevel addSecondLevelsObject:editingObject];
    }
    NSManagedObjectContext *context = editingObject.managedObjectContext;
    NSError *error = nil;
    if (![context save:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
    [self.navigationController dismissModalViewControllerAnimated:YES];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

- (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

@end

テーブルビューコントローラーの作成

最後に2レベル目のリストを表示するテーブルビューを追加し、元のRootViewControllerともども表示や新規オブジェクトの追加、選択への反応、 編集イベントへの対応などのコードを書いてゆきます。 テーブルビューの扱いに慣れている人の場合、NSFetchedResultsControllerを使うより、 NSFetchRequestを直接コンテキストで実行して配列を得てしまった方が、新たにNSFetchedResultsControllerDelegateの扱いを覚える手間を減らせますし、 何より今後拡張を行う場合でも手慣れた方法が使えるため楽です。 そのため今回はNSFetchedResultsControllerは使わず、NSFetchRequestを使ってNSManagedObjectContextから表示用の配列を得るコードを書いています。 そのためどちらもviewWillAppear:でデータ格納配列を作り、テーブルの更新を行わせるようにしています。 ナビゲーションバーの右側には編集ボタンを表示し、編集モードでは左側にオブジェクト追加のボタンを表示するようにしています。 そして追加のボタンが押されたら名前の編集用のビューを開くようにしています。編集モード以外では項目の選択で下の階層へ遷移するようにし、 編集モードでは名前を編集できるようにテキストフィールドがあるモーダルビューを表示します。 あとは項目の削除に対応させれば完成です。 以下にそれぞれのヘッダファイルと、メソッドファイルを掲載します。

//
// RootViewController.h
// FirstCoreData
//
// Created by Konton on 09/11/06.
// Copyright 2009 Konton. All rights reserved.
//


@interface RootViewController : UITableViewController {

    NSManagedObjectContext *managedObjectContext;
    NSMutableArray *listContent;
}

@property (nonatomic, retain) NSMutableArray *listContent;
@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;

@end

//
// RootViewController.m
// FirstCoreData
//
// Created by Konton on 09/11/06.
// Copyright 2009 Konton. All rights reserved.
//

#import "RootViewController.h"
#import "SecondLevelViewController.h"
#import "RootLevel.h"
#import "SecondLevel.h"
#import "EditNameViewController.h"

@implementation RootViewController

@synthesize listContent;
@synthesize managedObjectContext;

#pragma mark -
#pragma mark Memory management

- (void)dealloc {
    [listContent release];
    [managedObjectContext release];
    [super dealloc];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

#pragma mark -
#pragma mark View lifecycle

- (void)viewDidLoad {
    [super viewDidLoad];
    // ナビゲーションバーにタイトルを付けます。
    self.title = @"FirstCoreData";
    // ナビゲーションバーの右に編集ボタンを追加します。
    self.navigationItem.rightBarButtonItem = self.editButtonItem;
    // 編集時のセルの選択を許可します。
    self.tableView.allowsSelectionDuringEditing = YES;
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    // フェッチ用のリクエストを作成します。
    NSFetchRequest *request = [[NSFetchRequest alloc]init];
    // フェッチ対象のオブジェクトはRootLevelオブジェクトにします。
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"RootLevel"
        inManagedObjectContext:managedObjectContext];
    [request setEntity:entity];
    // RootLevelオブジェクト内のキー"name"で並べ替えを行わせます。
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:&sortDescriptor count:1];
    [request setSortDescriptors:sortDescriptors];
    // フェッチを実行します
    NSError *error = nil;
    NSArray *sortArray = [managedObjectContext executeFetchRequest:request error:&error];
    if(error) {
        NSLog(@"Error %@, %@", error, [error userInfo]);
        abort();
    }
    [request release];
    // 結果をNSMutableArrayに変換し、テーブルビュー表示用の配列にします。
    NSMutableArray *mutableSortArray = [[NSMutableArray alloc]initWithArray:sortArray];
    self.listContent = mutableSortArray;
    [sortDescriptor release];
    [sortDescriptors release];
    [mutableSortArray release];
    // テーブルビューを現在のlistContentを使って更新します。
    [self.tableView reloadData];
}

- (void)viewDidUnload {
    // Relinquish ownership of anything that can be recreated in viewDidLoad or on demand.
    // For example: self.myOutlet = nil;
}

#pragma mark -
#pragma mark Add a new object

- (void)add:(id)sender {
    // コンテキストに新規のRootLevelエンティティのオブジェクトを挿入します。
    NSManagedObject *newManagedObject =
    [NSEntityDescription insertNewObjectForEntityForName:@"RootLevel"
    inManagedObjectContext:managedObjectContext];
    // 編集用のビューを作ります。
    EditNameViewController *controller = [[[EditNameViewController alloc] init] autorelease];
    controller.editingObject = newManagedObject;
    // 新規作成です。
    controller.newObject = YES;
    controller.title = @"new object";
    // モーダルビューを表示します。
    UINavigationController *secondNavigationController =
        [[UINavigationController alloc] initWithRootViewController:controller];
    [self.navigationController presentModalViewController:secondNavigationController animated:YES];
    [secondNavigationController release];
}

#pragma mark -
#pragma mark Table view methods

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [listContent count];
}

// テーブルビューに表示するセルを構築します。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
            reuseIdentifier:CellIdentifier] autorelease];
    }
    // セルにオブジェクトのキー"name"に入っている値を表示します。
    NSManagedObject *managedObject = [listContent objectAtIndex:indexPath.row];
    cell.textLabel.text = [managedObject valueForKey:@"name"];
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    RootLevel *selectedRootLevel = (RootLevel *)[listContent objectAtIndex:indexPath.row];
    if(self.editing) {
        EditNameViewController *controller = [[[EditNameViewController alloc] init] autorelease];
        // 編集対象のオブジェクトを渡します。
        controller.editingObject = selectedRootLevel;
        // 編集対象は新規作成オブジェクトではありません。
        controller.newObject = NO;
        controller.title = @"edit object";
        // モーダルビューを表示します。
        UINavigationController *secondNavigationController =
            [[UINavigationController alloc] initWithRootViewController:controller];
        [self.navigationController presentModalViewController:secondNavigationController animated:YES];
        [secondNavigationController release];
    } else {
        // 選択したオブジェクト内のSecondLevelオブジェクトリストを表示します。
        SecondLevelViewController *controller =
            [[SecondLevelViewController alloc] initWithStyle:UITableViewStylePlain];
        controller.managedObjectContext = managedObjectContext;
        controller.rootLevel = selectedRootLevel;
        controller.title = selectedRootLevel.name;
        [self.navigationController pushViewController:controller animated:YES];
        [controller release];
    }
}

// 現状では編集モードで出るアイコンは削除のみです。
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
    forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        // Delete the managed object for the given index path
        RootLevel *deleteRootLevel = [listContent objectAtIndex:indexPath.row];
        // 選択したオブジェクト内の関連するSecondLevelオブジェクトもコンテキストから削除します。
        for(SecondLevel *secondLevel in deleteRootLevel.secondLevels) {
            [managedObjectContext deleteObject:secondLevel];
        }
        // このテーブルビューのリストから選択したオブジェクトを削除します。
        [listContent removeObject:deleteRootLevel];
        // コンテキストから選択したオブジェクトを削除します。
        [managedObjectContext deleteObject:deleteRootLevel];
        // コンテキストを保存します。
        NSError *error = nil;
        if (![managedObjectContext save:&error]) {
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }
        // テーブルビューから選択した行を削除します。
        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
    }
}

- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
    [super setEditing:editing animated:animated];
    [self.navigationItem setHidesBackButton:editing animated:animated];
    [self.tableView setEditing:editing animated:YES];
    if (editing) {
        // オブジェクト追加用のボタンを表示します。
        UIBarButtonItem *addButton = [[UIBarButtonItem alloc]
            initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(add:)];
        [self.navigationItem setLeftBarButtonItem:addButton animated:YES];
        [addButton release];
    } else {
        // オブジェクト追加用のボタンを隠します。
        [self.navigationItem setLeftBarButtonItem:nil animated:YES];
    }
}

@end

下のレベルについてもほぼ同様になりますが、表示するSecondLevelオブジェクトデータは上のレベルで選択されたRootLevelオブジェクトと関連があるものだけに限定されます。 また、現在はこれより下のレベルが無いので、編集モード以外で選択しても選択を取り消すだけにします。

//
// SecondLevelViewController.h
// FirstCoreData
//
// Created by Konton on 09/11/06.
// Copyright 2009 Konton. All rights reserved.
//

#import <UIKit/UIKit.h>

@class RootLevel;

@interface SecondLevelViewController : UITableViewController {

    RootLevel *rootLevel;
    NSManagedObjectContext *managedObjectContext;
    NSMutableArray *listContent;
}

@property (nonatomic, retain) NSMutableArray *listContent;
@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
@property (nonatomic,assign) RootLevel *rootLevel;

@end

//
// SecondLevelViewController.m
// FirstCoreData
//
// Created by Konton on 09/11/06.
// Copyright 2009 Konton. All rights reserved.
//

#import "SecondLevelViewController.h"
#import "RootLevel.h"
#import "SecondLevel.h"
#import "EditNameViewController.h"


@implementation SecondLevelViewController

@synthesize managedObjectContext;
@synthesize listContent;
@synthesize rootLevel;

- (void)dealloc {
    // rootLevelはassignなのでreleaseしません。
    [managedObjectContext release];
    [listContent release];
    [super dealloc];
}

// このクラスはInterfaceBuilderを使わずに作っています。
- (id)initWithStyle:(UITableViewStyle)style {
    if (self = [super initWithStyle:style]) {
        // ナビゲーションバーの右に編集ボタンを追加します。
        self.navigationItem.rightBarButtonItem = self.editButtonItem;
        // 編集時のセルの選択を許可します。
        self.tableView.allowsSelectionDuringEditing = YES;
    }
    return self;
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    // RootViewControllerと同様、キー"name"で並べ替えます。
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:&sortDescriptor count:1];
    // 選択されたrootLevelのsecondLevelsにあるオブジェクト群(NSSet)からテーブルビュー表示用の配列を作ります。
    NSMutableArray *sortArray = [[NSMutableArray alloc] initWithArray:[rootLevel.secondLevels allObjects]];
    // 並べ替えを実行してlistContentに格納します。
    [sortArray sortUsingDescriptors:sortDescriptors];
    self.listContent = sortArray;
    [sortDescriptor release];
    [sortDescriptors release];
    [sortArray release];
    // listContentでテーブルビューを更新します。
    [self.tableView reloadData];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

- (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

#pragma mark -
#pragma mark Add a new object

- (void)add:(id)sender {
    // コンテキストに新規のSecondLevelエンティティのオブジェクトを挿入します。
    NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:@"SecondLevel"
        inManagedObjectContext:managedObjectContext];
    // 編集用のビューを作ります。
    EditNameViewController *controller = [[[EditNameViewController alloc]init] autorelease];
    controller.editingObject = newManagedObject;
    // 新規作成です。
    controller.newObject = YES;
    controller.title = @"new object";
    // モーダルビューを表示します。
    UINavigationController *secondNavigationController =
        [[UINavigationController alloc] initWithRootViewController:controller];
    [self.navigationController presentModalViewController:secondNavigationController animated:YES];
    [secondNavigationController release];
}

#pragma mark Table view methods

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [listContent count];
}

// テーブルビューに表示するセルを構築します。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
            reuseIdentifier:CellIdentifier] autorelease];
    }
    // NSManagedObjectの継承クラスSecondLevelで書くとこうなります。
    SecondLevel *secondLevel = [listContent objectAtIndex:indexPath.row];
    cell.textLabel.text = secondLevel.name;
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    SecondLevel *selectedSecondLevel = (SecondLevel *)[listContent objectAtIndex:indexPath.row];
    if(self.editing) {
        EditNameViewController *controller = [[[EditNameViewController alloc] init] autorelease];
        // 編集対象のオブジェクトを渡します。
        controller.editingObject = selectedSecondLevel;
        // 編集対象は新規オブジェクトではありません。
        controller.newObject = NO;
        controller.title = @"edit object";
        // モーダルビューを表示します。
        UINavigationController *secondNavigationController =
            [[UINavigationController alloc] initWithRootViewController:controller];
        [self.navigationController presentModalViewController:secondNavigationController animated:YES];
        [secondNavigationController release];
    }
}

// 現状では編集モードで出るアイコンは削除のみです。
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
    forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        SecondLevel *secondLevel = [listContent objectAtIndex:indexPath.row];
        // このテーブルビューのリストから選択したオブジェクトを削除します。
        [listContent removeObject:secondLevel];
        // コンテキストから選択したオブジェクトを削除します。
        [managedObjectContext deleteObject:secondLevel];
        NSError *error = nil;
        if (![managedObjectContext save:&error]) {
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }
        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
    }
}

- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
    [super setEditing:editing animated:animated];
    [self.navigationItem setHidesBackButton:editing animated:animated];
    [self.tableView setEditing:editing animated:YES];
    if (editing) {
        // オブジェクト追加用のボタンを表示します。これにより編集中はバックボタンが隠れます。
        UIBarButtonItem *addButton = [[UIBarButtonItem alloc]
            initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(add:)];
        [self.navigationItem setLeftBarButtonItem:addButton animated:YES];
        [addButton release];
    } else {
        // オブジェクト追加用のボタンを隠します。
        [self.navigationItem setLeftBarButtonItem:nil animated:YES];
    }
}

@end

これらの変更を加えてビルドしてみると、前回のアプリケーションとは異なり、2レベルを行き来する形のデータベースを自在に編集できるアプリケーションへと拡張されます。 なお、前回ビルドしたときに作られたsqliteファイルが残っていると、データモデルの形式が違うというエラーが出てしまいます。 シミュレーターや実機から前回のアプリケーションを一回削除してからビルドとインストールをするようにしてください。 説明しやすいように今回の変更は全部InterfaceBuilderを一切使わずに行いましたが、何種類もの値を同時に編集するタイプのアプリケーションでは、 データ編集画面をInterfaceBuilderで製作したほうが楽でしょう。今回は文字列(NSString)のデータしか扱いませんでしたが、数値(論理値含む)はNSNumber、日付はNSDate、バイナリ値はNSDataオブジェクトとして格納されます。

今回掲載したコードではなるべく理解しやすいようにとビューコントローラー側でほとんどの処理を行わせています。また、手抜きでautoreleaseを使ってしまっている部分があったりしますがご容赦を。 あと、個人的な趣味でdeallocをメソッドファイルの頭に移動しています。これは自分がコードを書く場合だと@synthesizeの追加とdeallocへのreleaseの追加をワンセットで行うことが多いため、近くにあると便利だからです。 なお、削除などの処理の一部はオブジェクト側にメッセージを投げて行わせる方法もあります。 2レベル目の変更が1レベル目のデータに大きな影響を与える場合や、より深い階層構造を持つデータベースを構築する場合などは、オブジェクト側にコードを集中させていった方が保守の手間がかからなくなります。 例えば6階層のデータの場合、トップの階層のViewControllerに下の5階層を削除+対象オブジェクトを削除という処理を書き、以下それぞれの階層に別々に同様の削除処理を書くのなら、 それぞれのNSManagedObject派生クラス自体に「自分より一つ下の階層の削除用メソッドを呼んでから自分自身をコンテキストから消す」という仕組みの削除用メソッドをそれぞれのクラスに書いておき、 ViewController側からは対象のオブジェクトにある削除用メソッドを呼ぶだけで、そのオブジェクトの下層にあるオブジェクトを全てコンテキストから消すことが出来ます。 こうしておけば例えば一番上の階層のオブジェクトを削除する場合、1階層目のViewControllerから1階層目のオブジェクトにある削除メソッドを呼ぶだけで、

1階層目のオブジェクトは自身に関連のある2階層目の全てのオブジェクトの削除メソッドを呼ぶ

 2階層目のオブジェクトは自身に関連のある3階層目の全てのオブジェクトの削除メソッドを呼ぶ

  3階層目のオブジェクトは自身に関連のある4階層目の全てのオブジェクトの削除メソッドを呼ぶ

   4階層目のオブジェクトは自身に関連のある5階層目の全てのオブジェクトの削除メソッドを呼ぶ

    5階層目のオブジェクトは自身に関連のある6階層目の全てのオブジェクトの削除メソッドを呼ぶ

     6階層目が自身をコンテキストから消す

    6階層目がコンテキストから消えた後、5階層目が自身をコンテキストから消す

   5階層目がコンテキストから消えた後、4階層目が自身をコンテキストから消す

  4階層目がコンテキストから消えた後、3階層目が自身をコンテキストから消す

 3階層目がコンテキストから消えた後、2階層目が自身をコンテキストから消す

2階層目がコンテキストから消えた後、1階層目が自身をコンテキストから消す

という順番で勝手に処理が行われるようになります。このような仕組みを利用すれば、各階層のViewControllerにはこの削除メソッドの呼び出しと、自分がその時テーブル表示用に持っている配列からそのオブジェクトを消す処理を書くだけで済むようになり、全部の処理をそれぞれのViewController側だけでやらせるよりコード量を減らすことができます。

前へ
Copyright© 2009 Konton All rights reserved. - このサイトについて - サイトマップ

Valid XHTML 1.1 正当なCSSです!