iOSで、データの保存/参照/変更/削除を行う場合、CoreDataを使うらしい。という情報を得て、いいチュートリアルが無いか探しました。Appleが提供しているチュートリアルで、日本語訳されているものがあったので、それを使うことにします。2010年末のドキュメントなので、例によってXcode4.2で使う場合には、幾つかの苦難が待ち受けていることが予想されます。。
◆
それではまず、チュートリアルをダウンロードします。
Appleの公式ドキュメントの日本語訳は「iOS Developer Library」にあります。古い情報が多いですが、定期的に「これは!」とアップルが思うドキュメントを追加/更新しているようです。RSSも購読可能なので、ぱんは登録してウォッチしています。
今回利用するチュートリアルは下記となります;
→
【PDF】『iOS Core Dataチュートリアル 〜Core Data Tutorial for iOS〜』
◆
1)プロジェクトの作成(P.10)
・Xcodeを起動します。

・「Empty Application」テンプレートを選択し「Next」。

・「Product Name」を「Locations」。「Class Prefix」にも「Locations」を入力。「Use Core Data」のチェックボックスをチェックし「Next」。
・保存先を適当に指定して「Create」。
2)「CoreLocation」フレームワークへのリンク設定(P.10)
・「Locations」を起動した際の初期画面にある、ヘッダナビゲーションの「Build Phases」をクリック。
・「Link Binary With Libraries」を開いて、その左下コーナーに現れる「+」をクリック。
・「CoreLocation.framework」を選択し、「Add」。
3)RootViewControllerクラスの作成(P.17)
・Xcodeメニュー「File >New >New File...」を選択。

・開いたウインドウの、左ペイン「iOS」の「Cocoa Touch」を選択し、右ペイン「UIViewController」を選択し、「Next」。

・「Class」を「RootViewController」と入力し、「With XIB for user interface」のチェックボックスのチェックが外れていることを確認して、「Next」。
・開いたダイアログの右下にある「Create」をクリック。
4)RootViewController.h の編集(P.18)
▼下記コードに、内容を書き換える
-------
#import <CoreLocation/CoreLocation.h> //「<」「>」は半角で
// クラス宣言→ @interface クラス名:スーパークラス <デリゲート(指定した別のクラスにメッセージを丸投げできるよう設定する)>
@interface RootViewController : UITableViewController <CLLocationManagerDelegate> { //「<」「>」は半角で
// インスタンス変数の宣言
NSMutableArray *eventsArray; // イベント配列
NSManagedObjectContext *managedObjectContext; // 管理オブジェクトコンテキスト
CLLocationManager *locationManager; // Core Locationマネージャ
UIBarButtonItem *addButton; // 追加ボタン
}
// メソッドの宣言(ここでは、setter & getterを作るオマジナイを記述)
@property (nonatomic, retain) NSMutableArray *eventsArray;
@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain) CLLocationManager *locationManager;
@property (nonatomic, retain) UIBarButtonItem *addButton;
@end
// ▼参考
//『Objective-CでのDelegateとは』
http://konton.ninpou.jp/program/cocoa/delegate.html
//『@propertyでnonatomic指定していいの?』
http://ringsbell.blog117.fc2.com/blog-entry-350.html
//『Objective-Cの @property と @synthesize の組み合わせが何をやっているのかを解説』
http://d.hatena.ne.jp/nakamura001/20101101/1288632739
-------
5)RootViewController.m に、setter & getter関連記述を追加(P.18〜19)
▼「@implementation RootViewController」の直下に下記コード追加;
-------
// setter & getter を作るオマジナイを記述
@synthesize eventsArray;
@synthesize managedObjectContext;
@synthesize locationManager;
@synthesize addButton;
//
// ▼参考
//『Objective-Cの @property と @synthesize の組み合わせが何をやっているのかを解説』
// →
http://d.hatena.ne.jp/nakamura001/20101101/1288632739
-------
6)RootViewController.m に、「Core Locationマネージャ」を動的に作成するアクセサメソッドを作成(P.19)
▼「5」の直下に、下記コード追加;
-------
// インスタンスメソッド定義→ -(戻り値の型) メソッド名 : (引数の型) 引数名
- (CLLocationManager *)locationManager {
if (locationManager != nil) {
return locationManager;
}
locationManager = [[CLLocationManager alloc] init];
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
locationManager.delegate = self;
return locationManager;
}
//
// ▼参考
//『メソッド呼びだし』
http://ja.wikipedia.org/wiki/Objective-C#.E3.83.A1.E3.82.BD.E3.83.83.E3.83.89.E5.91.BC.E3.81.B3.E3.81.A0.E3.81.97
//『メソッド定義とメッセージ式』
http://www.atmarkit.co.jp/fcoding/articles/objc/03/objc03b.html
//
//『CLLocationManager 〜iPhoneアプリ開発の虎の巻〜』
http://iphone-tora.sakura.ne.jp/cllocationmanager.html
//『<公式ガイド>位置情報対応プログラミングガイド』
http://developer.apple.com/jp/devcenter/ios/library/documentation/LocationAwarenessPG.pdf
//『GPSを利用する方法』
http://d.hatena.ne.jp/ntaku/20090228/1235816377
-------
7)RootViewController.m に、「追加(Add)」ボタンを状況に応じ、有効または無効にする2つのデリゲートメソッドを実装(P.19)
▼「6」の直下に、下記コード追加;
-------
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
addButton.enabled = YES;
}
- (void)locationManager:(CLLocationManager *)manager
didFailWithError:(NSError *)error {
addButton.enabled = NO;
}
//
// ▼参考
//『CLLocationで現在位置を取得する』
http://iphone-app-developer.seesaa.net/article/128902019.html
-------
8)RootViewController.m に、「viewDidLoad」メソッドを実装(P.19〜20)
▼「7」の直下に、下記コード追加;
-------
- (void)viewDidLoad {
[super viewDidLoad];
// タイトルを設定
self.title = @"Locations";
//「Edit」ボタンのセットアップ
self.navigationItem.leftBarButtonItem = self.editButtonItem;
//「+」ボタンのセットアップ(非活性ボタンにして配置)
addButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self action:@selector(addEvent)];
addButton.enabled = NO;
self.navigationItem.rightBarButtonItem = addButton;
// ロケーションマネージャを起動する。
[[self locationManager] startUpdatingLocation];
}
-------
※テンプレート標準の「viewDidLoad」メソッドのコメントアウト部分は、一律削除しておく
9)RootViewController.m に、「viewDidUnload」&「dealloc」メソッドを実装(P.20)
-------
- (void)viewDidUnload {
self.eventsArray = nil;
self.locationManager = nil;
self.addButton = nil;
}
- (void)dealloc {
[managedObjectContext release];
[eventsArray release];
[locationManager release];
[addButton release];
[super dealloc];
}
//
// ▼参考
//『UIViewController のメモリ管理まとめ』
http://hamasyou.com/blog/archives/000384
// → init で確保したメモリは dealloc で破棄する
// → viewDidload で確保したメモリは dealloc で破棄する。メモリ不足警告時には、didReceiveMemoryWarning メソッドから呼び出される、viewDidUnload で破棄する。
-------
※テンプレート標準の「viewDidUnload」メソッド関連の記述は、一律削除しておく
10)LocationsAppDelegate.h の編集(P.20)
▼下記コードに、内容を書き換える
-------
#import <UIKit/UIKit.h> //「<」「>」は半角で
// クラス宣言→ @interface クラス名:スーパークラス <デリゲート(指定した別のクラスにメッセージを丸投げできるよう設定する)>
@interface LocationsAppDelegate : UIResponder <UIApplicationDelegate> //「<」「>」は半角で
//『UIWindow 〜iPhoneアプリ開発の虎の巻〜』 http://iphone-tora.sakura.ne.jp/uiwindow.html
@property (strong, nonatomic) UIWindow *window;
@property (nonatomic, retain) UINavigationController *navigationController;
// 下記「▼参考」参照
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;
@end
UINavigationController *navigationController;
// ▼参考
//『Core Data Overview : 重要なクラス』
http://blog.natsuapps.com/2010/09/core-data-overview_5352.html
//・「Managed Object Context」
http://cocoadevcentral.com/articles/000086.php#9
//・「Managed Object Model」
http://cocoadevcentral.com/articles/000086.php#3
//・「NSPersistentStoreCoordinator」
http://cocoadevcentral.com/articles/000086.php#10
-------
11)LocationsAppDelegate.m に、ヘッダファイルのインポート追加&Navigation Controllerプロパティの合成(P.21)
▼「#import "LocationsAppDelegate.h"」の直下に、下記コード追加;
-------
#import "RootViewController.h"
-------
▼「@synthesize window = _window;」の直下に、下記コード追加;
-------
@synthesize navigationController;
-------
12)「- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{ }」の編集(P.21)
※ チュートリアル記載のコードのままでは、Xcode4.2 で動作しないので注意
▼ 下記内容に書き換える
-------
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// チュートリアルでは削除されてるけど、ここの部分はXcode4.2の「Empty Application」テンプレートだと必須
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
RootViewController *rootViewController = [[RootViewController alloc] initWithStyle:UITableViewStylePlain];
NSManagedObjectContext *context = [self managedObjectContext];
if (!context) {
// エラーを処理する
}
// 管理オブジェクトコンテキストをView Controllerに渡す
rootViewController.managedObjectContext = context;
UINavigationController *aNavigationController = [[UINavigationController alloc]
initWithRootViewController:rootViewController];
self.navigationController = aNavigationController;
//チュートリアルでは「window」だけど、「_window」に変更してます
[_window addSubview:[navigationController view]];
[_window makeKeyAndVisible];
[rootViewController release];
[aNavigationController release];
return YES;
}
-------
13)テストビルド実行
・特に問題なければ「Run」をクリックすると、開発途中のアプリのビューが表示されるはずです。
・でも、ぱんの環境では、原因不明のエラーが出ました。
「This generally means that another instance of this process was already running or is hung in the debugger」というメッセージがデバッガーに表示されます。Terminalからアプリを消しても、改善されません。何度も同じプロジェクトをぐりぐりいじってたので、iPhoneシミュレータがうまく処理できなくなったようです。
・Mac自体を再起動してから、「Run」してiPhoneシミュレーターをみると、、、

・きちんと表示されました。
参考)
http://ndevmemo.blog.shinobi.jp/Entry/83/
※ ちなみに、Lion+Xcode4.2の場合、iPhoneシミュレータが使うアプリは「 /Users/(ユーザ名)/Library/Application Support/iPhone Simulator/5.0/Applications/xxxxxxxxxxxx/ 以下に配置されています
◆
14)CoreDataで「Event」エンティティを作成(P.23〜25)
・Xcodeの左カラムから「Locations.xcdatamodel」ファイルを選択

・メインカラムの下部「+ ( Add Entity)」をクリック
→ フォーカスされている「Entity」名に、「Event」と入力
・メインカラム「Attributes」コーナーの左下「+」をクリック
→ フォーカスされている「Attribute」名に、「creationDate」と入力
→ 「Type」の選択肢から、「Date」を選択
・再度、「+」をクリック
→ フォーカスされている「Attribute」名に、「latitude」と入力
→ 「Type」の選択肢から、「Double」を選択
・再々度、「+」をクリック
→ フォーカスされている「Attribute」名に、「longitude」と入力
→ 「Type」の選択肢から、「Double」を選択
15)「Event」エンティティに対応する「NSManagedObject subclass」を作成(P.26)
・「14」の状態で(「Event」エンティティが選択されたままで)、メニュー「File> New> New File...」を選択

・開いたダイアログの左カラムで、「iOS」の「Core Data」を選択し、右カラム「NSManagedObject subclass」を選択し、「Next」
・保存場所の指定。特にオプションなど指定せず、「Create」

・「Event.h」と「Event.m」が生成されます(中身も、勝手に入ります!)
16)「RootViewController.m」に「Event.h」をインポート(P.27)
▼「#import "RootViewController.h"」の直後に、下記コード追加
-------
#import "Event.h"
-------
17)「RootViewController.h」に「addEvent」メソッドを宣言(P.29)
▼「@end」の直前に、下記コード追加
-------
- (void)addEvent;
-------
18)「RootViewController.m」に「addEvent」メソッドを追加(P.29〜32)
▼「9」の直後に、下記コード追加
-------
- (void)addEvent {
CLLocation *location = [locationManager location]; //「locationManager」は、CLLocationManagerクラスのインスタンス
if (!location) {
return; }
//「Event」エンティティの新規インスタンスを作成
Event *event = (Event *)[NSEntityDescription insertNewObjectForEntityForName:@"Event" inManagedObjectContext:managedObjectContext];
//「cordinate」は、CLLocationクラスのプロパティ。英語では「座標」という意味を持つ
CLLocationCoordinate2D coordinate = [location coordinate];
//「event」インスタンスに値を設定
[event setLatitude:[NSNumber numberWithDouble:coordinate.latitude]];
[event setLongitude:[NSNumber numberWithDouble:coordinate.longitude]];
[event setCreationDate:[NSDate date]];
//
//▼参考)
//『CLLocation Class リファレンス』
http://developer.apple.com/library/ios/#DOCUMENTATION/CoreLocation/Reference/CLLocation_Class/CLLocation/CLLocation.html#//apple_ref/doc/uid/TP40007126
//『CLLocationManager Class リファレンス』
http://developer.apple.com/library/ios/#documentation/CoreLocation/Reference/CLLocationManager_Class/CLLocationManager/CLLocationManager.html
NSError *error = nil;
if (![managedObjectContext save:&error]) {
// エラーを処理する
}
//場所を指定(atIndex:)して、要素(event)を挿入
[eventsArray insertObject:event atIndex:0];
// RowとSectionが「0」のNSIndexPathを作って、indexPathインスタンスに代入する
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
//
//▼参考)
//『UITableViewにcellを動的に追加/削除する2つの方法。』
http://www.cocoalife.net/2010/04/post_543.html
// 『UITableViewをスクロールしたときに、一番下のセルがすべて出るよう吸着自動スクロールさせる方法』
http://ssdkfk.wordpress.com/2011/08/18/uitableviewをスクロールしたときに、一番下のセルがすべ/
//『iOS Table Viewプログラミングガイド(PDF)』
http://developer.apple.com/jp/devcenter/ios/library/documentation/TableView_iPhone.pdf
}
-------
※「Event *event = (Event *)[NSEntityDescription insertNewObjectForEntityForName:@"Event" inManagedObjectContext:managedObjectContext];」の「(Event *)」って、変わった使い方ですね。。リファレンスとかみても、よくわかりませんでした。 →
http://d.hatena.ne.jp/jjj777/00000523/recID9437
19)「RootViewController.m」に「tableView:numberOfRowsInSection:」メソッドを追加(P.32)
▼「18」の直後に、下記コード追加
-------
// ロード時に呼び出され、セクションに含まれるセル数を返す
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return [eventsArray count];
}
-------
20)「RootViewController.m」に「tableView:(UITableView *)tableView cellForRowAtIndexPath:」メソッドを追加(P.32)
▼「19」の直後に、下記コード追加
-------
// ロード時に呼び出され、セルの内容を返す
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// タイムスタンプ用の日付フォーマッタ
static NSDateFormatter *dateFormatter = nil; if (dateFormatter == nil) {
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
}
// 緯度と経度用の数値フォーマッタ
static NSNumberFormatter *numberFormatter = nil; if (numberFormatter == nil) {
numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
[numberFormatter setMaximumFractionDigits:3];
}
static NSString *CellIdentifier = @"Cell";
// 新規セルをデキューまたは作成する
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
Event *event = (Event *)[eventsArray objectAtIndex:indexPath.row];
cell.textLabel.text = [dateFormatter stringFromDate:[event creationDate]];
NSString *string = [NSString stringWithFormat:@"%@, %@",
[numberFormatter stringFromNumber:[event latitude]],
[numberFormatter stringFromNumber:[event longitude]]]; cell.detailTextLabel.text = string;
return cell;
//
// ▼参考)
//『UITableViewCell Class Reference』
http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UITableViewCell_Class/Reference/Reference.html
}
-------
21)テストビルド実行(P.33)
・「Run」をクリックすると、開発途中のアプリのビューが表示される。はずなのですが、再度エラーが発生しました。
・デバッガには「reason=The model used to open the store is incompatible with the one used to create the store」というメッセージが表示されています。どうやら、CoreDataが先ほど作成したsqliteと、整合性がとれなくなっているようです。
→ 参考)『iPhoneアプリ開発 備忘録:Core Data Navigation-Based 2月6日』
http://ameblo.jp/mattmatsui/entry-10452051631.html
・ぱん(Lion+Xcode4.2)の場合「/Users/(ユーザ名)/Library/Application Support/iPhone Simulator/5.0/Applications/xxxxxxxxxxxxxxxxxx/Documents」の中にある「Locations.sqlite」をターミナルから削除して、再度 iPhoneシミュレータを終了して、再度「Run」すると解決しました。
・下記コードを「RootViewController.m」の「- (void)viewDidLoad」の「}」の直前に挿入してビルド(Run)すると「+」ボタンでセルが追加されるようになります。あくまでテスト用コードなので、ビルドで内容を確認したら、コメントアウトします。
-------
//▼テストビルド(21)用コード
eventsArray = [[NSMutableArray alloc] init]; //テストが終わったら、この行はコメントアウトすること!
-------
◆
22)「RootViewController.m」の「viewDidLoad」メソッドを編集(P.36〜37)
▼「8」の最後「}」の直前に、下記コード追加
-------
// sqliteへの「request」を準備(内容無し)
NSFetchRequest *request = [[NSFetchRequest alloc] init];
//「Event」エンティティの「managedObjectContext」を「entity」にセット
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Event" inManagedObjectContext:managedObjectContext];
//「request」に「entity」を代入
[request setEntity:entity];
//「sortDescriptor」に、ソート順指定の情報を代入
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"creationDate" ascending:NO];
//「sortDescriptor」を、「sortDescriptors」に代入(変換)
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor,nil];
//「request」に「ソート情報(sortDescriptors)」を代入
[request setSortDescriptors:sortDescriptors];
// 割り当て済みのオブ ジェクトを解放
[sortDescriptors release];
[sortDescriptor release];
//「request」を実行し、結果を可変コピーして、可変配列「mutableFetchResults」に代入
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// エラーを処理する
}
//View Controller(self)に、「mutableFetchResults」を設定
[self setEventsArray:mutableFetchResults];
// 割り当て済みオブジェクトを解放
[mutableFetchResults release];
[request release];
// ▼参考
//『NSFetchRequest Class Reference』
http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/CoreDataFramework/Classes/NSFetchRequest_Class/NSFetchRequest.html
//・Core Dataのデータをフェッチするためのリクエストである。このオブジェクトを利用して、保存領域(persistent store)からデータを
// 読み出し、その結果をFetched results controllerに保存する
// →『Core Data 勉強日記 (4)』
http://blog.natsuapps.com/2010/02/core-data-4.html
//『NSSortDescriptor Class Reference』
http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSSortDescriptor_Class/Reference/Reference.html
//『オブジェクトを列挙してNSArrayを作りたい』
http://konton.ninpou.jp/program/cocoa/dataobject/nsarray.html#iwo
-------
23)「RootViewController.m」に「tableView:commitEditingStyle:forRowAtIndexPath:」メソッドを追加(P.39)
▼「20」の直後に、下記コード追加
-------
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// 指定のインデックスパスにある管理オブジェクトを削除する。
NSManagedObject *eventToDelete = [eventsArray objectAtIndex:indexPath.row];
[managedObjectContext deleteObject:eventToDelete];
// 配列とTable Viewを更新する。
[eventsArray removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
// 変更をコミットする。
NSError *error = nil;
if (![managedObjectContext save:&error]) {
// エラーを処理する。
}
}
}
//『UITableViewDataSource Protocol Reference』
http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UITableViewDataSource_Protocol/Reference/Reference.html#//apple_ref/occ/intfm/UITableViewDataSource/tableView:commitEditingStyle:forRowAtIndexPath:
// その他参考)
http://ameblo.jp/xcc/entry-10468549510.html
-------
24)「Run」でビルドして、実行
以上で、終了です。
▼自分用TODO:CoreDataの参考資料として、下記も目を通す;
・『CoreData』
http://www.gamvaro.com/kswiki/index.php?OS関係%2FiPhone用OS(IOS)%2FCoreData
・『CoreDataの構成』
http://iphone-dev.g.hatena.ne.jp/hao_yayoi/
◆以下、ソース一覧 -----------------------------------
▼「LocationsAppDelegate.h」
-------
#import <UIKit/UIKit.h> //「<」「>」は半角で
// クラス宣言→ @interface クラス名:スーパークラス <デリゲート(指定した別のクラスにメッセージを丸投げできるよう設定する)>
@interface LocationsAppDelegate : UIResponder <UIApplicationDelegate> //「<」「>」は半角で
//『UIWindow 〜iPhoneアプリ開発の虎の巻〜』 http://iphone-tora.sakura.ne.jp/uiwindow.html
@property (strong, nonatomic) UIWindow *window;
@property (nonatomic, retain) UINavigationController *navigationController;
// 下記「▼参考」参照
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;
@end
UINavigationController *navigationController;
// ▼参考
//『Core Data Overview : 重要なクラス』 http://blog.natsuapps.com/2010/09/core-data-overview_5352.html
//・「Managed Object Context」 http://cocoadevcentral.com/articles/000086.php#9
//・「Managed Object Model」 http://cocoadevcentral.com/articles/000086.php#3
//・「NSPersistentStoreCoordinator」http://cocoadevcentral.com/articles/000086.php#10
-------
▼「LocationsAppDelegate.m」
-------
#import "LocationsAppDelegate.h"
#import "RootViewController.h"
@implementation LocationsAppDelegate
@synthesize window = _window;
@synthesize navigationController;
@synthesize managedObjectContext = __managedObjectContext;
@synthesize managedObjectModel = __managedObjectModel;
@synthesize persistentStoreCoordinator = __persistentStoreCoordinator;
- (void)dealloc
{
[_window release];
[__managedObjectContext release];
[__managedObjectModel release];
[__persistentStoreCoordinator release];
[super dealloc];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// チュートリアルでは削除されてるけど、ここの部分はXcode4.2の「Empty Application」テンプレートだと必須
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
RootViewController *rootViewController = [[RootViewController alloc] initWithStyle:UITableViewStylePlain];
NSManagedObjectContext *context = [self managedObjectContext];
if (!context) {
// エラーを処理する
}
// 管理オブジェクトコンテキストをView Controllerに渡す
rootViewController.managedObjectContext = context;
UINavigationController *aNavigationController = [[UINavigationController alloc]
initWithRootViewController:rootViewController];
self.navigationController = aNavigationController;
//チュートリアルでは「window」だけど、「_window」に変更してます
[_window addSubview:[navigationController view]];
[_window makeKeyAndVisible];
[rootViewController release];
[aNavigationController release];
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application
{
/*
Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
*/
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
/*
Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
*/
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
/*
Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
*/
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
/*
Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
*/
}
- (void)applicationWillTerminate:(UIApplication *)application
{
// Saves changes in the application's managed object context before the application terminates.
[self saveContext];
}
- (void)saveContext
{
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil)
{
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error])
{
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
*/
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
}
}
#pragma mark - Core Data stack
/**
Returns the managed object context for the application.
If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
*/
- (NSManagedObjectContext *)managedObjectContext
{
if (__managedObjectContext != nil)
{
return __managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil)
{
__managedObjectContext = [[NSManagedObjectContext alloc] init];
[__managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return __managedObjectContext;
}
/**
Returns the managed object model for the application.
If the model doesn't already exist, it is created from the application's model.
*/
- (NSManagedObjectModel *)managedObjectModel
{
if (__managedObjectModel != nil)
{
return __managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Locations" withExtension:@"momd"];
__managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return __managedObjectModel;
}
/**
Returns the persistent store coordinator for the application.
If the coordinator doesn't already exist, it is created and the application's store added to it.
*/
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (__persistentStoreCoordinator != nil)
{
return __persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Locations.sqlite"];
NSError *error = nil;
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
{
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
Typical reasons for an error here include:
* The persistent store is not accessible;
* The schema for the persistent store is incompatible with current managed object model.
Check the error message to determine what the actual problem was.
If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
If you encounter schema incompatibility errors during development, you can reduce their frequency by:
* Simply deleting the existing store:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
* Performing automatic lightweight migration by passing the following dictionary as the options parameter:
[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
*/
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
return __persistentStoreCoordinator;
}
#pragma mark - Application's Documents directory
/**
Returns the URL to the application's Documents directory.
*/
- (NSURL *)applicationDocumentsDirectory
{
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
@end
-------
▼「RootViewController.h」
-------
#import <CoreLocation/CoreLocation.h> //「<」「>」は半角で
// クラス宣言→ @interface クラス名:スーパークラス <デリゲート(指定した別のクラスにメッセージを丸投げできるよう設定する)>
@interface RootViewController : UITableViewController <CLLocationManagerDelegate> { //「<」「>」は半角で
// インスタンス変数の宣言
NSMutableArray *eventsArray; // イベント配列
NSManagedObjectContext *managedObjectContext; // 管理オブジェクトコンテキスト
CLLocationManager *locationManager; // Core Locationマネージャ
UIBarButtonItem *addButton; // 追加ボタン
}
// メソッドの宣言(ここでは、setter & getterを作るオマジナイを記述)
@property (nonatomic, retain) NSMutableArray *eventsArray;
@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain) CLLocationManager *locationManager;
@property (nonatomic, retain) UIBarButtonItem *addButton;
- (void)addEvent;
@end
// ▼参考
//『Objective-CでのDelegateとは』 http://konton.ninpou.jp/program/cocoa/delegate.html
//『@propertyでnonatomic指定していいの?』 http://ringsbell.blog117.fc2.com/blog-entry-350.html
//『Objective-Cの @property と @synthesize の組み合わせが何をやっているのかを解説』 http://d.hatena.ne.jp/nakamura001/20101101/1288632739
-------
▼「RootViewController.m」
-------
#import "RootViewController.h"
#import "Event.h"
@implementation RootViewController
// setter & getter を作るオマジナイを記述
@synthesize eventsArray;
@synthesize managedObjectContext;
@synthesize locationManager;
@synthesize addButton;
//
// ▼参考
//『Objective-Cの @property と @synthesize の組み合わせが何をやっているのかを解説』
// → http://d.hatena.ne.jp/nakamura001/20101101/1288632739
// インスタンスメソッド定義→ -(戻り値の型) メソッド名 : (引数の型) 引数名
- (CLLocationManager *)locationManager {
if (locationManager != nil) {
return locationManager;
}
locationManager = [[CLLocationManager alloc] init];
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
locationManager.delegate = self;
return locationManager;
}
//
// ▼参考
//『メソッド呼びだし』 http://ja.wikipedia.org/wiki/Objective-C#.E3.83.A1.E3.82.BD.E3.83.83.E3.83.89.E5.91.BC.E3.81.B3.E3.81.A0.E3.81.97
//『メソッド定義とメッセージ式』 http://www.atmarkit.co.jp/fcoding/articles/objc/03/objc03b.html
//
//『CLLocationManager 〜iPhoneアプリ開発の虎の巻〜』 http://iphone-tora.sakura.ne.jp/cllocationmanager.html
//『<公式ガイド>位置情報対応プログラミングガイド』 http://developer.apple.com/jp/devcenter/ios/library/documentation/LocationAwarenessPG.pdf
//『GPSを利用する方法』 http://d.hatena.ne.jp/ntaku/20090228/1235816377
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
addButton.enabled = YES;
}
- (void)locationManager:(CLLocationManager *)manager
didFailWithError:(NSError *)error {
addButton.enabled = NO;
}
//
// ▼参考
//『CLLocationで現在位置を取得する』http://iphone-app-developer.seesaa.net/article/128902019.html
- (void)viewDidLoad {
[super viewDidLoad];
// タイトルを設定
self.title = @"Locations";
//「Edit」ボタンのセットアップ
self.navigationItem.leftBarButtonItem = self.editButtonItem;
//「+」ボタンのセットアップ(非活性ボタンにして配置)
addButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self action:@selector(addEvent)];
addButton.enabled = NO;
self.navigationItem.rightBarButtonItem = addButton;
// ロケーションマネージャを起動する。
[[self locationManager] startUpdatingLocation];
//▼テストビルド(21)用コード
//eventsArray = [[NSMutableArray alloc] init];
// sqliteへの「request」を準備(内容無し)
NSFetchRequest *request = [[NSFetchRequest alloc] init];
//「Event」エンティティの「managedObjectContext」を「entity」にセット
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Event" inManagedObjectContext:managedObjectContext];
//「request」に「entity」を代入
[request setEntity:entity];
//「sortDescriptor」に、ソート順指定の情報を代入
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"creationDate" ascending:NO];
//「sortDescriptor」を、「sortDescriptors」に代入(変換)
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor,nil];
//「request」に「ソート情報(sortDescriptors)」を代入
[request setSortDescriptors:sortDescriptors];
// 割り当て済みのオブ ジェクトを解放
[sortDescriptors release];
[sortDescriptor release];
//「request」を実行し、結果を可変コピーして、可変配列「mutableFetchResults」に代入
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// エラーを処理する
}
//View Controller(self)に、「mutableFetchResults」を設定
[self setEventsArray:mutableFetchResults];
// 割り当て済みオブジェクトを解放
[mutableFetchResults release];
[request release];
// ▼参考
//『NSFetchRequest Class Reference』 http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/CoreDataFramework/Classes/NSFetchRequest_Class/NSFetchRequest.html
//・Core Dataのデータをフェッチするためのリクエストである。このオブジェクトを利用して、保存領域(persistent store)からデータを
// 読み出し、その結果をFetched results controllerに保存する
// →『Core Data 勉強日記 (4)』 http://blog.natsuapps.com/2010/02/core-data-4.html
//『NSSortDescriptor Class Reference』 http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSSortDescriptor_Class/Reference/Reference.html
//『オブジェクトを列挙してNSArrayを作りたい』 http://konton.ninpou.jp/program/cocoa/dataobject/nsarray.html#iwo
}
- (void)viewDidUnload {
self.eventsArray = nil;
self.locationManager = nil;
self.addButton = nil;
}
- (void)dealloc {
[managedObjectContext release];
[eventsArray release];
[locationManager release];
[addButton release];
[super dealloc];
}
//
// ▼参考
//『UIViewController のメモリ管理まとめ』 http://hamasyou.com/blog/archives/000384
// → init で確保したメモリは dealloc で破棄する
// → viewDidload で確保したメモリは dealloc で破棄する。メモリ不足警告時には、didReceiveMemoryWarning メソッドから呼び出される、viewDidUnload で破棄する。
- (void)addEvent {
CLLocation *location = [locationManager location]; //「locationManager」は、CLLocationManagerクラスのインスタンス
if (!location) {
return; }
//「Event」エンティティの新規インスタンスを作成
Event *event = (Event *)[NSEntityDescription insertNewObjectForEntityForName:@"Event" inManagedObjectContext:managedObjectContext];
//「cordinate」は、CLLocationクラスのプロパティ。英語では「座標」という意味を持つ
CLLocationCoordinate2D coordinate = [location coordinate];
//「event」インスタンスに値を設定
[event setLatitude:[NSNumber numberWithDouble:coordinate.latitude]];
[event setLongitude:[NSNumber numberWithDouble:coordinate.longitude]];
[event setCreationDate:[NSDate date]];
//
//▼参考)
//『CLLocation Class リファレンス』 http://developer.apple.com/library/ios/#DOCUMENTATION/CoreLocation/Reference/CLLocation_Class/CLLocation/CLLocation.html#//apple_ref/doc/uid/TP40007126
//『CLLocationManager Class リファレンス』 http://developer.apple.com/library/ios/#documentation/CoreLocation/Reference/CLLocationManager_Class/CLLocationManager/CLLocationManager.html
NSError *error = nil;
if (![managedObjectContext save:&error]) {
// エラーを処理する
}
//場所を指定(atIndex:)して、要素(event)を挿入
[eventsArray insertObject:event atIndex:0];
// RowとSectionが「0」のNSIndexPathを作って、indexPathインスタンスに代入する
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
//
//▼参考)
//『UITableViewにcellを動的に追加/削除する2つの方法。』 http://www.cocoalife.net/2010/04/post_543.html// 『UITableViewをスクロールしたときに、一番下のセルがすべて出るよう吸着自動スクロールさせる方法』 http://ssdkfk.wordpress.com/2011/08/18/uitableviewをスクロールしたときに、一番下のセルがすべ/
//『iOS Table Viewプログラミングガイド(PDF)』 http://developer.apple.com/jp/devcenter/ios/library/documentation/TableView_iPhone.pdf
}
// ロード時に呼び出される。セクションに含まれるセル数を返す
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return [eventsArray count];
}
// ロード時に呼び出され、セルの内容を返す
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// タイムスタンプ用の日付フォーマッタ
static NSDateFormatter *dateFormatter = nil; if (dateFormatter == nil) {
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
}
// 緯度と経度用の数値フォーマッタ
static NSNumberFormatter *numberFormatter = nil; if (numberFormatter == nil) {
numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
[numberFormatter setMaximumFractionDigits:3];
}
static NSString *CellIdentifier = @"Cell";
// 新規セルをデキューまたは作成する
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
Event *event = (Event *)[eventsArray objectAtIndex:indexPath.row];
cell.textLabel.text = [dateFormatter stringFromDate:[event creationDate]];
NSString *string = [NSString stringWithFormat:@"%@, %@",
[numberFormatter stringFromNumber:[event latitude]],
[numberFormatter stringFromNumber:[event longitude]]]; cell.detailTextLabel.text = string;
return cell;
//
// ▼参考)
//『UITableViewCell Class Reference』 http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UITableViewCell_Class/Reference/Reference.html
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// 指定のインデックスパスにある管理オブジェクトを削除する。
NSManagedObject *eventToDelete = [eventsArray objectAtIndex:indexPath.row];
[managedObjectContext deleteObject:eventToDelete];
// 配列とTable Viewを更新する。
[eventsArray removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
// 変更をコミットする。
NSError *error = nil;
if (![managedObjectContext save:&error]) {
// エラーを処理する。
}
}
}
//『UITableViewDataSource Protocol Reference』 http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UITableViewDataSource_Protocol/Reference/Reference.html#//apple_ref/occ/intfm/UITableViewDataSource/tableView:commitEditingStyle:forRowAtIndexPath:
// その他参考) http://ameblo.jp/xcc/entry-10468549510.html
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
/*
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView
{
}
*/
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
@end
-------
▼「Event.h」
-------
#import <Foundation/Foundation.h> //「<」「>」は半角で
#import <CoreData/CoreData.h> //「<」「>」は半角で
@interface Event : NSManagedObject
@property (nonatomic, retain) NSDate * creationDate;
@property (nonatomic, retain) NSNumber * longitude;
@property (nonatomic, retain) NSNumber * latitude;
@end
-------
▼「Event.m」
-------
#import "Event.h"
@implementation Event
@dynamic creationDate;
@dynamic longitude;
@dynamic latitude;
@end
-------
・・・以上。なにかご意見や不具合などありましたら、コメント欄や、Twitter(@toaster4us)でご連絡ください。