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の表示に反映されない。スクロールして再描画をさせると表示が更新される。原因わからず。