サルでもわかる Core Data 入門【実装編】
サルでもわかる Core Data 入門【概念編】の続きです。今回は実際に Core Data を使ったプログラムを作成します。
サンプルアプリの概要
アドレス帳アプリを作りながら Core Data の使い方を説明していきます。
以下はサンプルアプリの画面構成です。
連絡先一覧画面と連絡先詳細画面の2画面構成になっています。
サンプルアプリのソースコードはこちらで公開しています。
開発の流れ
サンプルアプリの開発の流れは以下のようになります。
- プロジェクトの作成
- モデルクラスの作成
- エンティティの定義とエンティティとモデルクラスの関連付け
- ストーリーボード(Storyboard)を使ってビューコントローラの遷移と画面デザインを作成する
- 連絡先詳細画面の開発
- 連絡先一覧画面の開発
プロジェクトの作成
それでは初めにプロジェクトを作成しましょう。プロジェクトの作成手順は以下のようになります。
- Xcode の File メニューから New > New Project... を選択してください。
- Master-Detail Application を選択して Next ボタンを押してください。
- Use Storyboard と Use Core Data、Use Automatic Reference Counting にチェックが入っていることを確認して Product Name と Company Identifer を入力後 Next ボタンを押してください。例ではそれぞれ AddressBook、sample と入力しています。
- 最後にプロジェクトの保存場所を指定して Create ボタンを押してください。
プロジェクトの構成
作成されたプロジェクトの構成は以下のようになります。
それでは実際のプログラムを見ていきましょう。
AppDelegate クラスの確認
AppDelegate クラスの中を見ていきましょう。AppDelegate.h を開いてください。Core Data を使わないプロジェクトと比べると以下のプロパティとメソッドが追加されているのが確認できます。
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext; @property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel; @property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator; - (void)saveContext; - (NSURL *)applicationDocumentsDirectory;
処理も色々と追加されていますが AppDelegate クラスを修正する必要はありません。このクラスでデータの保存場所の設定やエンティティとモデルクラスのマッピングを管理する AddressBook.xcdatamodeld ファイルの設定を行っています。
モデルクラスの作成
モデルクラスを作成していきましょう。プロジェクトに Person クラスと Address クラスを追加します。プロジェクトの AddressBook グループを選択してコンテクストメニューの New File... で新しいクラスを追加します。
まずはじめに Person クラスを作成します。親クラスには NSManagedObject クラスを指定してください。
#import <CoreData/CoreData.h> #import "Address.h" @interface Person : NSManagedObject @property (strong, nonatomic) NSString *name; @property (strong, nonatomic) Address *address; @end
NSManagedObject のサブクラスを作る時は @synthesize ではなく @dynamic を使います。
#import "Person.h" @implementation Person @dynamic name, address; @end
次に Address クラスを作成します。
#import <CoreData/CoreData.h> @interface Address : NSManagedObject @property (strong, nonatomic) NSString *zipCode; @property (strong, nonatomic) NSString *state; @property (strong, nonatomic) NSString *city; @property (strong, nonatomic) NSString *other; @end
#import "Address.h" @implementation Address @dynamic zipCode, state, city, other; @end
エンティティの作成
AddressBook.xcdatamodeld を開いてエンティティの定義をします。まずはファイルに元からある Event エンティティを削除してください。その後、左下にある Add Entity ボタンを押して Person エンティティと Address を追加します。
属性(Attribute)の定義
Person エンティティに name という名前の String 型の属性を追加します。
同じように Address エンティティにそれぞれ zipCode, state, city, other という名前で String 型の属性を追加します。
関連(Relationship)の定義
Person エンティティに address という名前で関連を追加します。Destination には Address エンティティを指定します。
同じように Address エンティティにも person という名前で関連を追加します。Destination には Person エンティティを指定します。
エンティティとクラスの関連付けを定義
CONFIGURATIONS の下にある Default を選択してください。Person エンティティの Class に Person クラス、Address エンティティの Class に Address クラスを入力します。
ビューコントローラの遷移と画面デザインの作成
ストーリーボードを使ってビューコントローラの遷移と画面デザインを作成していきます。ビューコントローラの遷移と画面デザインは MainStoryboard_iPhone.storyboard ファイルに定義されていますので、このファイルを開いて修正していきます。初めに Detail View Controller に UI 部品を配置していきます。"Detail view content goes here" と表示されている UILabel を削除後、以下のように UI 部品を配置してください。
次に連絡先一覧画面の「+」ボタンが押された時のビューコントローラの遷移の設定をします。Master View Controller の下に表示されている2つのアイコンのうち右側のアイコンを選択してください。アイコンを選択したらキーボードの control キーを押しながらマウスで Detail View Controller までドラッグします。
マウスのボタンをはなすと Storyboard Segues という小さなポップアップが表示されます。この中の push という項目を選択してください。
Master View Controller と Detail View Controller の間の矢印が一本増えたのが確認できます。2本の矢印のうち下の矢印を選択してください。選択すると右の詳細設定画面に Storyboard Segue という項目が表示されます。この中に Identifier という入力欄がありますのでそこに createDetail と入力してください。
※segue(セグウェイ)という聞き慣れない単語が出てきますがこれは「ある状態から次の状態へ滑らかに移行する」という意味の動詞です。推測ですが、少し前に話題になった乗り物Segway は segue が語源だと思います。
DetailViewController クラスの修正
DetailViewController クラスを修正していきます。このクラスは連絡先詳細画面のためのコントローラクラスです。
この画面で連絡先の新規作成と既存連絡先の編集を行います。
プロパティの修正と追加
DetailViewController.h を開いてプロパティの修正と追加を行います。まず detailItem プロパティの型を id から Person に変更します。次に、以下のようにプロパティを追加します。
@interface DetailViewController : UIViewController <UISplitViewControllerDelegate> @property (strong, nonatomic) Person *detailItem; @property (strong, nonatomic) NSManagedObjectContext *managedObjectContext; @property (strong, nonatomic) IBOutlet UIScrollView *scrollView; @property (strong, nonatomic) IBOutlet UITextField *nameField; @property (strong, nonatomic) IBOutlet UITextField *zipCodeField; @property (strong, nonatomic) IBOutlet UITextField *stateField; @property (strong, nonatomic) IBOutlet UITextField *cityField; @property (strong, nonatomic) IBOutlet UITextField *otherField; @end
scrollView, nameField, zipCodeField, stateField, cityField, otherField プロパティは MainStoryboard_iPhone.storyboard のUI 部品と IBOutlet 接続してください。
Done ボタンの追加とスクロールビューの調整
ここからは DetailViewController.m を修正していきます。まずはじめに Done ボタンの追加とスクロールビューの表示領域の設定を行います。Done ボタンは編集終了時に押すボタンです。64行目付近の viewDidLoad メソッドを以下のように修正してください。
- (void)viewDidLoad { [super viewDidLoad]; self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(done)] autorelease]; self.scrollView.contentSize = CGSizeMake(320, 800); [self configureView]; }
データの表示
データの表示を行う処理を追加します。テキストフィールドに連絡先一覧画面で選択した連絡先のデータをセットしていきます。DetailViewController.m の47行目付近の configureView メソッドを修正してください。
- (void)configureView { if (self.detailItem) { self.nameField.text = self.detailItem.name; self.zipCodeField.text = self.detailItem.address.zipCode; self.stateField.text = self.detailItem.address.state; self.cityField.text = self.detailItem.address.city; self.otherField.text = self.detailItem.address.other; } }
表示データの保存
Done ボタンが押された時の動きを実装します。まずはじめにテキストフィールドの内容を detailItem オブジェクトの属性にセットします。セットが終わったら managedObjectContext オブジェクトの save メソッドを呼んでデータを保存します。DetailViewController.m にdone メソッドを作成して以下の処理を追加してください。
- (void)done { self.detailItem.name = nameField.text; self.detailItem.address.zipCode = zipCodeField.text; self.detailItem.address.state = stateField.text; self.detailItem.address.city = cityField.text; self.detailItem.address.other = otherField.text; NSError *error = nil; if (![self.managedObjectContext save:&error]) { NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) { [self.navigationController popViewControllerAnimated:YES]; } }
MasterViewController クラスの修正
MasterViewController クラスを修正していきます。このクラスは連絡先一覧画面のためのコントローラクラスです。
連絡先一覧画面の機能は以下のようになっています。
- 連絡先の一覧表示
- 「+」ボタン押下で連絡先詳細画面に遷移
- 連絡先を選択したら連絡先詳細画面に遷移
- 「Edit」ボタン押下で連絡先を削除する機能
この機能のうち1と2に関してはプログラムを修正する必要があります。3と4は自動生成されたコードをそのまま使うことができます。またこのクラスはプロパティや公開メソッドの追加は行いませんので MasterViewController.h ファイルの修正は必要ありません。以降の修正は MasterViewController.m ファイルで行います。
連絡先の一覧表示
174行目付近の fetchedResultsController メソッドを修正していきます。自動生成されたコードでは各行に時間を表示するようになっているので、そこを時間ではなく名前を表示するように修正します。fetchedResultsController メソッドの以下の行を修正してください。
修正前
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Event" inManagedObjectContext:self.managedObjectContext];
修正後
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:self.managedObjectContext];
Event を Person に変更するだけです。NSEntityDescription クラスは AddressBook.xcdatamodeld ファイルで設定したエンティティの情報を管理するクラスです。entityForName: inManagedObjectContext: メソッドでエンティティの名前を指定してエンティティオブジェクトを生成します。
次に連絡先の表示順の設定をします。fetchedResultsController メソッドの以下の行を修正してください。
修正前
NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"timeStamp" ascending:NO] autorelease];
修正後
NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"name" ascending:NO] autorelease];
NSSortDescriptor クラスはデータの並び順を管理するクラスです。連絡先のデータを name の降順に並べます。
NSFetchRequest クラスや NSEntityDescription クラスについての説明はサルでもわかる Core Data 入門【概念編】を参照してください。
Person オブジェクトを生成する
Person オブジェクトを生成するための createNewPerson メソッドを作成します。初めに createNewPerson メソッドの定義を追加します。MasterViewController.m の14行目付近のクラス定義領域を以下のように修正します。
@interface MasterViewController () - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath; - (Person *)createNewPerson; @end
次に createNewPerson メソッドの処理を追加します。MasterViewController.m の最終行にある @end の1行前付近に createNewPerson メソッドを作成して以下の処理を追加してください。
- (Person *)createNewPerson { NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext]; NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity]; Person *newPerson = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context]; Address *address = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([Address class]) inManagedObjectContext:context]; newPerson.address = address; return newPerson; }
NSManagedObject クラスのサブクラスは NSEntityDescription クラスのメソッドを使ってオブジェクトを生成します。alloc と init と使ってオブジェクトを生成するとエラーになるので注意してください。詳細はサルでもわかる Core Data 入門【概念編】を参照してください。
「+」ボタン押下で連絡先詳細画面に遷移
次に「+」ボタンを押すと連絡先詳細画面に遷移するように修正します。insertNewObject メソッドは「+」ボタンが押された時に呼ばれるメソッドです。
performSegueWithIdentifier: sender: メソッドを呼ぶと第1引数に指定された文字列を使ってストーリーボードのセグエを検索します。セグエがあればそこに定義されているビューコントローラに遷移します。
それでは MasterViewController.m の283行目付近にある insertNewObject メソッドを以下のように修正してください。
- (void)insertNewObject { if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { self.detailViewController.detailItem = [self createNewPerson]; self.detailViewController.managedObjectContext = self.fetchedResultsController.managedObjectContext; } else { [self performSegueWithIdentifier:@"createDetail" sender:self]; } }
ビューコントローラ遷移時の処理を修正します。MasterViewController.m の164行目付近にある prepareForSegue: sender: メソッドを以下のように修正します。
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([[segue identifier] isEqualToString:@"showDetail"]) { NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow]; Person *selectedObject = [[self fetchedResultsController] objectAtIndexPath:indexPath]; [[segue destinationViewController] setDetailItem:selectedObject]; } else { [[segue destinationViewController] setDetailItem:[self createNewPerson]]; } [[segue destinationViewController] setManagedObjectContext:self.fetchedResultsController.managedObjectContext]; }
UIStoryboardSegue クラス型 segue オブジェクトの identifier プロパティには MainStoryboard_iPhone.storyboard ファイルの Storyboard Segue の Identifier で定義した文字列がわたってきます。連絡先の編集時には "showDetail"、連絡先の新規作成時には "createDetail" という文字列がわたります。
ビューコントローラ遷移時の詳しい動きを知りたい方は以下の記事を参考にしてください。
動作確認
以上でサンプルアプリの開発は終わりです。動作確認をしてアプリの動きをチェックしてください。