2009年12月11日金曜日

写真撮影後に viewDidLoad が呼び出される

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
UIImagePickerController を使って写真を撮影したのち、呼び出し元の UIViewConroller の viewDidLoadが呼び出されていることに気がついた。このメソッドはてっきり最初に一回だけ呼び出されるものと思っていたのでここへ初期化処理を書いておいたのだが、そのせいで写真撮影毎に初期化処理が呼び出されてしまった。また view上の UIImageView へ設定していた画像が表示されない。中身を確認すると nil になっていた。

調べてみると、どうもメモリ不足が原因で UIViewController が view を再作成しているのが原因ということだった。
2009-01-21 - f-shinの日記 - iPhoneアプリ開発グループ


実際、didReceiveMemoryWarning が呼び出されているのがわかった。

- (void)didReceiveMemoryWarning {
 // Releases the view if it doesn't have a superview.
 NSLog(@"didReceiveMemoryWarning");
 [super didReceiveMemoryWarning];
 // Release any cached data, images, etc that aren't in use.
}


おー、そうなのか。

参考本で紹介されていた下記のコードは使えないことがわかる(実際、OS v3.1.2の実機で画像が表示できない)。

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
[picker dismissModalViewControllerAnimated:YES];
photoView.image =
 [info objectForKey:UIImagePickerControllerOriginalImage];   
}


なぜなら photoView(UIImageView)はメモリ不足が生じた場合、この直後に再作成されるので。


対策方法の1つとしては viewを開放させない(再作成させない)ことが紹介されていた。
もう一つは viewが再作成されることを前提に実装する方法。


正攻法はやっぱり後者なんだろうな。
その場合、撮影した画像は UIViewController のプロパティへ直接設定するのではなく別の場所へ保存し、表示する時にそれを使うようにする必要がある。

2009年11月25日水曜日

画像を保存する

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
UIImagePickerController から取得した画像を保存する。

ファイル名

ユニークにしたいので何かランダムなものにしたい。UUID を使うことにする。


- (NSString*) stringWithUUID {
CFUUIDRef uuidObj = CFUUIDCreate(nil);//create a new UUID
//get the string representation of the UUID
NSString *uuidString = (NSString*)CFUUIDCreateString(nil, uuidObj);
CFRelease(uuidObj);
return [uuidString autorelease];
}

- (NSString*)createImageFilename
{
NSString* filename =
[NSString stringWithFormat:@"%@.jpg", [self stringWithUUID]];
return filename;
}



(参考)Cocoaの日々: UUID を作る - CFUUIDCreate と globallyUniqueString


保存先

標準のドキュメントフォルダ直下とする。


- (NSString*)getImageFilepath
{
NSArray* paths = NSSearchPathForDirectoriesInDomains(
NSDocumentDirectoryNSUserDomainMaskYES);
NSString* path = [paths objectAtIndex:0];
return [path stringByAppendingPathComponent:self.memo.photo];
}


保存処理

UIImageJPEGRepresentation を使う。


- (void)saveImage
{
NSData* data = UIImageJPEGRepresentation(self.photoView.image,0.5);
[data writeToFile:[self getImageFilepath] atomically:NO];
}

2009年11月20日金曜日

UIKit Functions

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
いわゆるユーティリティ系の関数があるのに気がついた。

画像系だとこんなのがある。

NSData * UIImageJPEGRepresentation (
UIImage *image,
   CGFloat compressionQuality
);
NSData * UIImagePNGRepresentation (
UIImage *image
);
void UIImageWriteToSavedPhotosAlbum (
UIImage  *image,
   id       completionTarget,
   SEL      completionSelector,
   void     *contextInfo
);

画像を保存する時に使えそう。


(追加)画像の処理については下記の記事が詳しい。
UIImagePickerController, UIImage, Memory and More! - Stack Overflow

基本的なことから画像の縮小や保存まで書かれていて参考になる。

2009年11月18日水曜日

キーボードの表示制御 - resignFirstResponder と透明なボタン

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
OneLiner の作成は細々と続いている。今はこんな感じ。






詳細情報のビューではタイトルとメモの入力、それとカメラ取り込みができるようになっている(まだできていないが)。


さて詳細情報のビュー(以下、詳細ビュー)では、何も工夫しないと下記の場合キーボードが出っぱなしになってしまう。

  • 前回テキストフィールドにフォーカスがあたった状態で、詳細ビューを開く
  • テキストフィールドにフォーカスをあててしまった

このままだと画像が見えなくて困るのでキーボードを隠す必要がある。はじめてのiPhoneプログラミング によればフォーカスを外せばキーボードは自動的に隠れるとのこと。そこで次のタイミングでフォーカスを外す事ににした。

  • 詳細ビューが隠れる時
  • テキストフィールド以外をタッチした時
フォーカスを外すには全てのテキストフィールドに対して resignFirstResponder を投げれば良い。その為の小さなメソッドを書いた。
- (void)hideKeyboard
{
[textTitle resignFirstResponder];
[textMemo resignFirstResponder];
}

これを各タイミングで呼出せば良い。詳細ビューが隠れる時は UIViewController の viewWillDisappear をオーバーライドする。
- (void)viewWillDisappear:(BOOL)animated
{
[self hideKeyboard];
}


なおビューが隠れた後では遅いので viewDidDisappear はこの目的では使えない。

「テキストフィールド以外をタッチした時」は、画面を覆う透明なボタンを作り、それがタッチされた時に先ほどの hideKeyboard を呼出せば良い。このボタンは Interface Builder で Round Rect Button を配置し、ビュー一杯に広げた後に Type を Custom に変えると透明なボタンになる。




さらに他のコントロールの下へ来る様に Layout メニューで Send To Back を選び、背面へ移動させる。
上記ははじめてのiPhoneプログラミングで紹介されていた方法。大味な感じもするが他に方法がわからないので従うことにする。

- - - -
逆にテキストフィールドへフォーカスをあてたい場合はどうすれば良いのだろうか。新規登録時にはタイトルのフィールドへ自動的にフォーカスをあてたい。

2009年11月9日月曜日

追加UIの変更 - UIBarButtonSystemItemAdd

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
メモを追加するユーザインターフェイスを一般的なものに変える。右上に十字「+」ボタンがでるやつ。メインのビューコントローラでボタンを追加する。

こんな感じ。

MainViewController.m

- (void)viewDidLoad {
  :

  UIBarButtonItem* addButton = [[UIBarButtonItem alloc]
    initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
       target:self action:@selector(addMemo:)];


  self.navigationItem.rightBarButtonItem = addButton;
  [addButton release];
}



こうなる。


2009年11月3日火曜日

メモの変更と追加

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
OneLiner開発続き。


今回はメモの変更と追加機能を追加する。



メモの変更

まず変更から。変更画面用のコントローラ DetailViewController と Xibを用意する。


DetailViewController.h
#import
#import

@interface DetailViewController : UIViewController {
NSManagedObject* _managedObject;
UITextField *_textTitle;
}

@property (nonatomic, retain) NSManagedObject* managedObject;
@property (nonatomic, retain) IBOutlet UITextField* textTitle;
@end



最初のリスト画面で項目を選んだらこの変更用ビューをナビゲーションスタックへ pushして表示する。この時選択された行の NSManagedObject をへ渡してやる。この時も fetchedResultController から目的の行を簡単に取り出せる。



MainViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
detailViewController.managedObject =
[fetchedResultsController objectAtIndexPath:indexPath];


[[self navigationController] pushViewController:detailViewController
  animated:YES];
}



DetailViewController では Saveボタンが押されたら変更された内容をこの NSManagedObject へ反映する。反映後にナビゲーションスタックを popして元の画面へ戻る。

DetailViewController.m
- (void)save:(id)sender
{
[self.managedObject setValue:self.textTitle.text forKey:@"title"];
NSError* error = nil;
[[self.managedObject managedObjectContext] save:&error];
NSLog(@"error=%@", error);
[self.navigationController popViewControllerAnimated:YES];
}

変更の結果を UITableView へ反映させる必要がある。この目的で NSFetchedResultsControllerDelegate が用意されている。このデリゲートのメソッドを実装して NSManagedObjectContext の変更を UITableView へ反映させる。

MainViewController.m

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {

switch(type) {

case NSFetchedResultsChangeInsert:
NSLog(@"NSFetchedResultsChangeInsert:");
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;

case NSFetchedResultsChangeDelete:
NSLog(@"NSFetchedResultsChangeDelete:");
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;

case NSFetchedResultsChangeUpdate:
NSLog(@"NSFetchedResultsChangeUpdate:");
[self configureCell:[self.tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;

case NSFetchedResultsChangeMove:
NSLog(@"NSFetchedResultsChangeMove:");
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
// Reloading the section inserts a new row and ensures that titles are updated appropriately.
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}


変更、挿入、削除など操作の種類によって UITableView を変更する。上記はDevCenterで配布されているサンプルコード CoreDataBooks が参考になった。



メモの追加

続いてメモの追加。トップ画面のテキストボックスへテキストを入力し、"POST"ボタンを押すと追加できるようにする。"POST"ボタンを post: アクションへ繋げて処理を書く。

MainViewController.m
- (IBAction)post:(id)sender
{
NSLog(@"Post message");
NSError* error = nil;
NSManagedObject* memo = [NSEntityDescription insertNewObjectForEntityForName:@"Memo"
  inManagedObjectContext:managedObjectContext];
[memo setValue:self.textField.text forKey:@"title"];
[managedObjectContext save:&error];
if (error) {
NSLog(@"ERROR: %@", error);
} else {
self.textField.text = @"";
[self.textField resignFirstResponder];
}
}


CoreData を使っていると簡単に追加できる。

追加時のログを見ると INSERT が投げられているのがわかる。
2009-11-03 07:29:57.705 OneLiner[15035:207] CoreData: sql: BEGIN EXCLUSIVE
2009-11-03 07:29:57.707 OneLiner[15035:207] CoreData: sql: SELECT Z_MAX FROM Z_PRIMARYKEY WHERE Z_ENT = ?
2009-11-03 07:29:57.708 OneLiner[15035:207] CoreData: sql: UPDATE Z_PRIMARYKEY SET Z_MAX = ? WHERE Z_ENT = ? AND Z_MAX = ?
2009-11-03 07:29:57.709 OneLiner[15035:207] CoreData: sql: COMMIT
2009-11-03 07:29:57.710 OneLiner[15035:207] CoreData: sql: BEGIN EXCLUSIVE
2009-11-03 07:29:57.711 OneLiner[15035:207] CoreData: sql: INSERT INTO ZMEMO(Z_PK, Z_ENT, Z_OPT, ZTITLE, ZPHOTO) VALUES(?, ?, ?, ?, ?)
2009-11-03 07:29:57.712 OneLiner[15035:207] CoreData: sql: COMMIT
2009-11-03 07:29:57.714 OneLiner[15035:207] CoreData: sql: pragma page_count
2009-11-03 07:29:57.714 OneLiner[15035:207] CoreData: annotation: sql execution time: 0.0006s
2009-11-03 07:29:57.715 OneLiner[15035:207] CoreData: sql: pragma freelist_count
2009-11-03 07:29:57.715 OneLiner[15035:207] CoreData: annotation: sql execution time: 0.0007s



UITableView のアクセサリ

UITableViewの右側に表示されている ">" アイコン。参考本に従ってデリゲートメソッド  tableView:accessoryTypeForRowWithIndexPath: を実装すると、実行時に WARNING が表示された。

2009-10-25 09:42:12.828 OneLiner[5235:207] WARNING: Using legacy cell layout due to delegate implementation of tableView:accessoryTypeForRowWithIndexPath: in .  Please remove your implementation of this method and set the cell properties accessoryType and/or editingAccessoryType to move to the new cell layout behavior.  This method will no longer be called in a future release.







- (UITableViewCellAccessoryType)tableView:(UITableView *)tableView accessoryTypeForRowWithIndexPath:(NSIndexPath *)indexPath
{
return UITableViewCellAccessoryDisclosureIndicator;
}

OS3.0からはデリゲート(テーブル単位)ではなくセル単位で指定できるようになった。UITableViewCell の accessoryTypeプロパティに設定してやれば良い。こんな感じ。


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *SimpleTableIdentifier = @"SimpleTableIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SimpleTableIdentifier];
if (cell == nil) {


cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
  reuseIdentifier:kCellIdentifier] autorelease];

cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
NSManagedObject* managedObject = [fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [managedObject valueForKey:@"title"];
return cell;
}

UITableViewCell の初期化

UITableViewCell の生成も OS3.0から変わっていて従来の initWithFrame:reuseIdentifier: ではなく、initWithStyle:reuseIdentifier: を使う(前者は Deprecated扱い)。セル内に複数の文字列を表示することが容易になった。



- - - -
変更、追加直後は UITableViewの表示に反映されない。スクロールして再描画をさせると表示が更新される。原因わからず。




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 の日々: 習作アプリ作成開始〜「一行メモ」

2009年9月30日水曜日

CoreData - テーブルの件数を取得する

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
(前回からの続き:Cocoa Touch の日々: CoreDataを使う

これから CoreData のデータを UITableView へ表示させる。UITableView へデータを表示するには最低2つの DataSourceメソッドを実装する必要がある。
- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;


これらのメソッドを実装するにあたり、まずはテーブルの件数を取得しよう。

ネットで探してみると "Effecient way to count entities" という記事が見つかった。
(参考)Cocoa CoreData effiecient way to count entities! - Stack Overflow

これを参考に件数取得のコードを書いてみた(というかそのまんまだが)。

- (NSUInteger)getCount
{
NSFetchRequest* request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:@"Memo"
inManagedObjectContext:managedObjectContext]];
[request setIncludesSubentities:NO];

NSError* error = nil;
NSUInteger count = [managedObjectContext countForFetchRequest:request error:&error];
if (count == NSNotFound) {
count = 0;
}
[request release];

return count;
}


managedObjectContext はメンバ変数として既に用意してあるものとする。
#NSError 変数は以前痛い目にあっているので必ず初期化しておく。



もし条件で絞り込みたい場合は Predicateなどが使える。
(参考)Cocoaの日々 - 2005年10月

2009年9月2日水曜日

CoreDataを使う

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
iPhone3.0から CoreData が使えるとのことだったので OneLiner で使ってみることにする。CoreDataは随分昔に MacOSXで使ったことがあるのでそのときの記事を引っ張りだしてきてコードを書いてみた。

Cocoaの日々(旧)
Core Data その1

CoreDataは永続性スタックと呼ばれるいくつかのクラスで構成されていて、利用時にはこれらのインスタンスを用意する必要がある。

(過去記事から引用)


コードはこんな感じ(とりあえず awakeFromNib に書いてみた)。
- (void)awakeFromNib
{
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:[NSManagedObjectModel mergedModelFromBundles:nil]];

managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator:persistentStoreCoordinator];

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];
NSLog(@"1:%@", error);

NSManagedObject* memo =
[NSEntityDescription insertNewObjectForEntityForName:@"Memo"
inManagedObjectContext:managedObjectContext];
[memo setValue:@"hello" forKey:@"title"];
[memo setValue:@"photo1.png" forKey:@"photo"];
[managedObjectContext save:&error];
NSLog(@"2:%@", error);
}


永続性スタックである NSPersistentStoreCoordinator, NSManagedObjectContext を用意し、必要な処理を加えていけば良い。以前の記事でも解説されているが、前者はモデル(いわゆるスキーマ)と(persistent object storeを通じて)DBファイルを扱い、後者はDBに対する検索、追加、変更、削除などのプログラミングのインターフェイスを提供している。今回永続化タイプは SQLite を選んだ。

モデルは Xcodeの「新規ファイル...」から作成できる。


今回は Memo というエンティティを1つ用意した。


さて実行してみよう。所定の場所に memo.db が作成されて(サンプルコードで挿入した)値が格納されているのがわかる。

2009年9月1日火曜日

Snow Leopard 付属の Xcode3.2 アップグレードすると iPhone開発ができなくなった

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
Snow Leopard 付属の Xcode3.2 アップグレードすると iPhone開発ができなくなった。iPhoneを接続してもオーガナイザになにも表示されない。

ネットを調べると Snow Leopard用の iPhone SDK が出ているとこのことだった。

iPhone Dev Center(要ログイン) へログインし、Downloadsから "iPhone SDK (Snow Leopard)" を選びダウンロードしインストールする。

すると iPhoneが認識され、無事にビルドすることができた。

- - - -

XCode3.2 ではフォントが変わった。

2009年8月21日金曜日

"Window-based Application" から組み立ててみる

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
一行メモの開発に入る。

新規プロジェクトを作成し "Window-based Applicatoin" を選択する。
名前を "OneLiner" とした。

この直後はデフォルトで用意されるアプリケーションデリゲートのクラスが1つできるだけ。
まずはビューを1つ追加してみよう。

新規ファイル作成を選び "UIViewController subclass"を選択する。
名前は "MainViewController" とする。
作成時のオプションで "With XIB for user interface" を入れておく。こうすると自動的にクラスの同名の XIBファイルを作り File's owner をそのクラスにしておいてくれる。

こんな感じ。



これらをつなげる作業に入ろう。
最終イメージはこう。



まずコードにプロパティとアウトレットを追加する。

OneLinerAppDelegate.h
@class MainViewController;
@interface OneLinerAppDelegate : NSObject {
UIWindow *window;
MainViewController* mainViewController;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet MainViewController* mainViewController;

@end


mainViewController が新規に加わった部分。


続いて MainWindow.XIB を開きアウトレットを接続する。

(1) MainWindow.XIB に UIViewController を追加し、クラスを MainViewController に変える
(2) OneLinerAppDelegate のアウトレット mainViewerController に(1)を接続する

さらに MainViewController.XIB を開きとりあえず簡単なコントロールを配置しておく。


最後にプログラムで window と ビューを接続する。
OneLinerAppDelegate.m
- (void)applicationDidFinishLaunching:(UIApplication *)application {    

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



さて実行してみよう。
ビューが表示された。




- - - -
これら一連の操作は実は Xcodeで新規プロジェクトを作成する時に "View-based Application" を選ぶと自動的に行われるもの。今回手動でやってみることでそれぞれの関係を理解することができた。