2009年10月24日土曜日

UINavigationController の導入

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
(前回)Cocoa Touch の日々: NSFetchedResultsController を使って CoreData を表示する

一覧画面で項目を選択するとその詳細画面を表示するようにしたい。今まではメインのウィンドウに1つのビューコントローラを直接割り当てていたが、UINavigationController を導入して、画面を切り替えるようにする。

(参考)「はじめての iPhoneプログラミング」- 「第9章 ナビゲーション・コントローラとテーブルビュー」

アプリケーションデリゲートのアウトレットを今までのビューコントローラから、UINavigationController へ変更する。

OnLinerAppDelegate.h

#import

@class MainViewController;
@interface OneLinerAppDelegate : NSObject {
    UIWindow *window;
    UINavigationController *mainViewController;
    UINavigationController *navigationController;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet MainViewController* mainViewController;
@property (nonatomicretainIBOutlet UINavigationController* navigationController;
@end



InterfaceBuilder を開き Navigation Controller を追加し、One Liner AppDelegate のアウトレットに接続する。従来あった MainViewController は削除する。

続いて UINavigationController の rootViewController に MainViewController を指定する。方法は、MainWindow.xib をリスト表示して Navigation Controller の下に現れる View Controller を書き換える。


後は AppDelegate の applicationDidFinishLaunching: このビューをウィンドウのビューとして設定すれば良い。


- (void)applicationDidFinishLaunching:(UIApplication *)application {  

    // Override point for customization after application launch
    [window addSubview:navigationController.view];
    [window makeKeyAndVisible];
}



これで終わり。
実行するとこんな感じになる。



画面上部にナビゲーション用のバー("Root View Controller")が表示されるようになる。


- - - -
次は詳細画面を作り、そこで編集が行える様にする。
(続く)

2009年10月13日火曜日

NSFetchedResultsController を使って CoreData を表示する

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
iPhone Dev Center から CoreDataBooks というサンプルコードを入手してソースを読む。なるほど NSFetchedResultsController を使うと UITableView で CoreData を使うのが楽になりそうだ。

CoreDataBooks の動作はこんな感じ。










リストに詳細表示、それと項目の編集を行う3つのビューから構成される。データの格納に CoreData を採用していて UITableView との連携に NSFetchedResultsController を使っている。

NSFetchedResultsController の使い方を理解するためにこのコードを参考にしつつ、(テーブルが一つで)構造が単純な OneLiner を NSFetcedResultsController を使うバージョンに書き換えてみる。

MainViewController.h

@interface MainViewController : UIViewController {

NSPersistentStoreCoordinator* persistentStoreCoordinator;
NSManagedObjectContext* managedObjectContext;
NSFetchedResultsController* fetchedResultsController;
}


メンバ変数に NSFechedResultsController を追加。


MainViewController.m

- (void)setupFetchedResultsController
{
// [1] Persistent Store Coordinator
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[NSManagedObjectModel mergedModelFromBundles:nil]];

NSError* error = nil;
NSString* filepath = [NSString stringWithFormat:@"%@/%@",
  [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0],
  @"memo.db"];

[persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
  URL:[NSURL fileURLWithPath:filepath]
  options:nil
error:&error];
if (error) {
NSLog(@"[1] %@", error);
}


// [2] Managed Object Context
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator:persistentStoreCoordinator];


// [3] Fetched Results Controller
// Fetch Request
NSFetchRequest* request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:@"Memo"
  inManagedObjectContext:managedObjectContext]];
[request setIncludesSubentities:NO];

// Sort Descriptor
NSSortDescriptor* sortDescriptor =
[[NSSortDescriptor alloc] initWithKey:@"title"
ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];

// Fetched Result Controller
fetchedResultsController =
[[NSFetchedResultsController alloc]
initWithFetchRequest:request
managedObjectContext:managedObjectContext
sectionNameKeyPath:nil
cacheName:@"Root"];
[sortDescriptor release];
[request release];
}



セットアップ用のメソッドを用意し、ここで NSPersistentStoreCoordinator, NSManagedObjectContext, NSFectedResultsController の3つのインスタンスを用意する。NSFetchedController には検索条件、ソート条件をあらかじめ渡しておく。


- (void)viewDidLoad {
    [super viewDidLoad];

[self setupFetchedResultsController];
NSError* error = nil;

if (![fetchedResultsController performFetch:&error]) {
NSLog(@"%@", error);
}
   :


View のロード時にセットアップと読み込み(performFetch)を実行する。

続いて DataSource のコーディング。

- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo =
[[fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *SimpleTableIdentifier = @"SimpleTableIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SimpleTableIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero
  reuseIdentifier:SimpleTableIdentifier] autorelease];
}
NSManagedObject* managedObject = [fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [managedObject valueForKey:@"title"];
return cell;
}

前回までは呼出される毎に検索をかけていたが、その部分を NSFetchedController にまかせることができるのですっきりした。UITableView 向けにつくられているので Section や NSIndexPath をそのまま扱えるのが便利。


実行してみよう。






動いた。


コンソールを見るとSQLが飛んでいるのは1回のみ(あたりまえだが)。


[Session started at 2009-10-13 06:13:39 +0900.]
2009-10-13 06:13:44.494 OneLiner[8886:207] CoreData: annotation: Connecting to sqlite database file at "/Users/hashi/Library/Application Support/iPhone Simulator/User/Applications/BF9B0A56-4CC4-428A-AB2B-FD1E20BDEAAD/Documents/memo.db"
2009-10-13 06:13:44.500 OneLiner[8886:207] CoreData: sql: pragma cache_size=1000
2009-10-13 06:13:44.501 OneLiner[8886:207] CoreData: sql: SELECT Z_VERSION, Z_UUID, Z_PLIST FROM Z_METADATA
2009-10-13 06:13:44.549 OneLiner[8886:207] CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZTITLE, t0.ZPHOTO FROM ZMEMO t0 ORDER BY t0.ZTITLE
2009-10-13 06:13:44.551 OneLiner[8886:207] CoreData: annotation: sql connection fetch time: 0.0019s
2009-10-13 06:13:44.552 OneLiner[8886:207] CoreData: annotation: total fetch execution time: 0.0032s for 18 rows.



SQLiteから取得したデータの管理を NSFetechedResultsController がやってくれるのでこれはいい。

2009年10月7日水曜日

UITableViewでCoreDataを効果的に扱える NSFetchedResultsController

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
MacOSX 版の Core Data のメソッド一覧(Core Data Framework Reference)と、iPhone版のそれを見比べると iPhone版の方がメソッドが多い事に気がついた。

一つは NSFetchedResultsController。


それとプロトコルが2つ。



NSFetechedResultsController の Overview によれば
This class is intended to efficiently manage the results returned from a Core Data fetch request to provide data for a UITableView object.
とある。

こんなクラスが追加されていたのか。
前回までのプログラミングで UITableView と CoreData を組み合わせるのは手間がかかるので何かうまい方法がないかと探していたが、やはりあったようだ。

サンプルもあるようなのでこれらを試してみよう。
(続く)

2009年10月5日月曜日

CoreData で発行されている SQL をデバッグ出力する

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
Mac Dev Center: Core Data Programming Guide: Core Data Performance

この中の "Analyzing Fetch Behavior with SQLite" に、コマンドライン引数に次を指定すると CoreDataから SQLite へ発行される SQL をデバッグ出力できるとの記述があった。

-com.apple.CoreData.SQLDebug 1



やってみよう。

実行可能ファイルの設定を開く。


「起動時に渡される引数」に上記の引数を追加する。



実行する。以下、例。
2009-10-05 06:50:48.557 OneLiner[1202:207] CoreData: sql: SELECT COUNT(*) FROM ZMEMO t0 
2009-10-05 06:50:48.558 OneLiner[1202:207] CoreData: annotation: total count request execution time: 0.0022s for count of 16.
2009-10-05 06:50:48.560 OneLiner[1202:207] CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZTITLE, t0.ZPHOTO FROM ZMEMO t0 ORDER BY t0.ZTITLE
2009-10-05 06:50:48.561 OneLiner[1202:207] CoreData: annotation: sql connection fetch time: 0.0010s
2009-10-05 06:50:48.562 OneLiner[1202:207] CoreData: annotation: total fetch execution time: 0.0023s for 16 rows.
2009-10-05 06:50:48.564 OneLiner[1202:207] CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZTITLE, t0.ZPHOTO FROM ZMEMO t0 ORDER BY t0.ZTITLE
:
:


これはいいな。
デバッグだけでなくパフォーマンスチューニングするのにこれは役立つ。

2009年10月3日土曜日

CoreData のデータを UITableView で表示する

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
(前回)Cocoa Touch の日々: CoreData - テーブルの件数を取得する


件数が取得できたので今度はデータを取得し、これを UITableView へ表示する。

CoreDataからのデータ取得方法は Mac Dev Center のガイドを参考にした。
Mac Dev Center: Core Data Programming Guide: Fetching Managed Objects


データ取得ができたのでこれを UITableView へ表示する。
まず DataSource メソッドの実装。

件数は前回作成したメソッドを使用する。
- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section
{
return [self getCount];
}


1行毎のデータは新規に -[getEntityAtIndex:] を用意し、これを呼出す。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *SimpleTableIdentifier = @"SimpleTableIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SimpleTableIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero
reuseIdentifier:SimpleTableIdentifier] autorelease];
}
NSUInteger row = [indexPath row];
NSManagedObject* managedObject = [self getEntityAtIndex:row];
cell.textLabel.text = [managedObject valueForKey:@"title"];
return cell;
}


なお UITableViewCell の textプロパティは iPhoneOS3.0よりDeprecated となっていた。その代わりに textLabelプロパティを使用する。

データ1行を取得するメソッドの実装。
- (NSManagedObject*)getEntityAtIndex:(NSUInteger)index
{
NSFetchRequest* request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:@"Memo"
inManagedObjectContext:managedObjectContext]];
[request setIncludesSubentities:NO];

NSSortDescriptor* sortDescriptor =
[[NSSortDescriptor alloc] initWithKey:@"title"
ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
[sortDescriptor release];

NSError* error = nil;
NSArray *array = [managedObjectContext executeFetchRequest:request error:&error];
if (array == nil) {
NSLog(@"%@", error);
return nil;
}
return [array objectAtIndex:index];
}


毎回全件持ってくるので効率が非常に悪いがとりあえずは動く事を優先させる(この辺りは動いた後に見直そう)。

実行してみる。

出た。


※データが同じものがたくさんあるが、実際そのように入れてある。

- - - -
サンプルの動作ではなく、実際に自分で組み立ててそれが動くとうれしい。やっぱりプログラミングは楽しい。


+++++ 参考本 +++++
はじめてのiPhoneプログラミング

+++++ 関連情報 +++++
Cocoa Touch の日々: CoreDataを使う
Cocoa Touch の日々: 習作アプリ作成開始〜「一行メモ」