Xcode で UITableView を使って Core Data を追加したり削除したり編集したりする iOS のアプリを作る

ほぼ 100% 個人的な備忘録。ARC オフ、IB 無しで作ってる。ようやく ARC が分かってきたので、そろそろ ARC も使ってみる予定。Apple の Core Data 関係の日本語ドキュメントのやりかたが Xcode 4.2 だと微妙に違ってるので、その部分を含めてメモ。

Core Data を使うプロジェクト作成の手順

  • Empty プロジェクトを作るものとする。
  • Project を作るときに Core Data にチェックを入れておく。
    • Core Data にチェックを入れると、xxx.xcdatamodeld というファイルが自動的に作成される。
    • ドキュメントでは自分で生成せよと書いてあるが、4.2 ではその必要はなくなった模様
  • .xcdatamodeld を選んで、Entity をひとつ生成する。
    • Entity に名前をつける (Event という名前にしたとする)
  • Event を選んで、Attribute を追加する。
    • attribute 毎に名前を型を選ぶ。
    • name: NSString を作ったとする
  • ここで New File で NSManagedObject のサブクラスを選び、場所を選んで Create する。
    • ドキュメントでは、"Event" を選んで Create せよとあるが、4.2 では Create する前に UI 上で Entity を選んでおくと、その Entity に対応するクラスが自動的に作成されるぽい。
    • 当然ながら Entity がひとつもないと生成できない。

ここまでやってから、以下のクラスを作成する。

  • DataViewController クラス (UITableViewController のサブクラス)
    • Event オブジェクトの一覧の表示と、削除や新規追加をする。
  • DataEditController クラス (UIViewController のサブクラス)
    • Event オブジェクトの値を変更するためのエディタ


ソース

  • AppDelegate.h

Xcode が自動的に生成するコードをそのまま使う。

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

// 以下すべて Xcode で自動的に生成される
@property (strong, nonatomic) UIWindow *window;

@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;

- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;
@end
  • AppDelegate.m

初期化メソッドのみ書きかえる。他のメソッドは Xcode が生成したものをそのまま使う。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];

    // Table View と Navigatinon View の生成
    DataViewController *table = [[DataViewController alloc] initWithStyle: UITableViewStylePlain ];
    UINavigationController *navi = [[UINavigationController alloc] initWithRootViewController: table];

    table.context = self.managedObjectContext;
    
    [table.navigationItem setTitle: @"CoreData"];
    [self.window setRootViewController: navi];
    
    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}
  • DataViewController.h
#import <UIKit/UIKit.h>

@interface DataViewController : UITableViewController
{
    NSManagedObjectContext *context;
    NSMutableArray *array;
}

@property (assign) NSManagedObjectContext *context;

@end
  • DataViewController.m

書きかえが必要なメソッドのみ列挙。

#import "DataViewController.h"
#import "DataEditController.h"
#import "Event.h"

@implementation DataViewController

@synthesize context;

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.clearsSelectionOnViewWillAppear = NO;

    // Edit ボタンを右肩に付ける
    self.navigationItem.rightBarButtonItem = self.editButtonItem;

    // Event オブジェクトをフェッチしてくる
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Event"
                                              inManagedObjectContext: context];
    [request setEntity:entity];
    
    NSError *error = nil;
    NSMutableArray *mutableFetchResults = [[context
                                            executeFetchRequest:request error:&error] mutableCopy];
    if (mutableFetchResults == nil) {
        // エラーの場合

        array = [[NSMutableArray alloc] init];
    }else{
        // retain 必須
        array = [mutableFetchResults retain];
    }
    
    [request release];
    [entity release];
}

// 新たな項目 (Event) を追加する
- (void)addEvent
{
    Event *event = (Event *)[NSEntityDescription 
                             insertNewObjectForEntityForName:@"Event" 
                             inManagedObjectContext: context];
    
    event.name = @"新しいイベント";
    event.level = 0;
    
    NSError *error = nil;
    if (![context save:&error]) {
        // エラーを処理する
        // 何もしてないけど
    }

    [array addObject:event ];

    // テーブルのビューを更新
    [self.tableView reloadData];
}

// 編集モードが切りかわったときの処理
- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
    [super setEditing:editing animated:animated];

    // 編集モードのときだけ、追加ボタンを表示させる
    if (editing) {
        UIBarButtonItem *addButton = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
                                                                                    target:self action:@selector(addEvent)] autorelease];
        [self.navigationItem setLeftBarButtonItem:addButton animated:YES]; // 追加ボタンを表示します。
    } else { 
        [self.navigationItem setLeftBarButtonItem:nil animated:YES]; // 追加ボタンを非表示にします。
    }
}

- (void)viewDidUnload
{
    [super viewDidUnload];

    [array release];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

#pragma mark - Table view data source

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

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [array 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];
    }
    
    Event *event = [array objectAtIndex: indexPath.row];
    [cell.textLabel setText: event.name ];
    
    return cell;
}


// return YES としないと、編集モードが ON にできない
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{   
    return YES;
}

// 削除総裁に対応させる
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        
        // 選択されたイベントを削除する
        NSManagedObject *eventToDelete = [array
                                          objectAtIndex:indexPath.row];
        [context deleteObject:eventToDelete];

        // 配列とTable Viewを更新する。
        [array removeObjectAtIndex:indexPath.row];

        // 変更をコミットする。
        NSError *error = nil;
        if (![context save:&error]) {
            // エラーを処理する。 
            // してないけど
        }
    
        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
    }

}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 編集用の下の階層の ViewController を作り、context と event オブジェクトをセットする。
    DataEditController *edit = [[DataEditController alloc] init];
    edit.context = context;
    edit.event = [array objectAtIndex: indexPath.row];

    // 下の階層に移動
    [self.navigationController pushViewController:edit animated: YES];

}

@end
  • DataEditController.h
#import <UIKit/UIKit.h>
#import "Event.h"

@interface DataEditController : UIViewController <UITextFieldDelegate>
{
    Event *event;
    UITextField* text;
    NSManagedObjectContext *context;
}

@property (assign) NSManagedObjectContext *context;
@property (retain) Event* event;
@end
  • DataEditController.m

これも変更したメソッドのみ。

#import "DataEditController.h"

@implementation DataEditController

@synthesize event;
@synthesize context;

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    UIView *v = [[UIView alloc] initWithFrame:CGRectMake(0,0,320,480)];

    // 文字列を入力するフィールドを作成
    text = [[UITextField alloc] initWithFrame:CGRectMake(20, 20, 280, 40)];
    text.text = event.name;
    text.returnKeyType = UIReturnKeyDone;
    [text setDelegate: self];
    
    [v addSubview: text];
    [self setView: v];
}

// キーボードで return キーが押されたときの処理
-(BOOL)textFieldShouldReturn:(UITextField*)textField
{
    // キーボードを閉じる
    [textField resignFirstResponder];

    // データを更新する
    event.name = text.text;    
    NSError *error = nil;
    if (![context save:&error]) {
        // エラーを処理する
        // 何もしてないけど
    }
    
    // ひとつ上の階層に戻る
    [self.navigationController popViewControllerAnimated: YES ];
    
    return YES;
}

@end