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