連載
» 2011年04月20日 00時00分 公開

SDKで始めるiPad/iPhoneアプリ開発の勘所(終):iOS 4の新機能13選&AssetsLibraryで作る画像ビューア (2/4)

[竹内彰吾,株式会社ビーブレイクシステムズ]

「AssetsLibrary」で画像ライブラリを自由自在に

 iOS 4で追加された「AssetsLibraryフレームワーク」を使ってフォトライブリから写真を読み込み、付加情報を付けて保存をする方法について解説します。連載第5回の「Core Graphicsで作るiPad向けお絵かきアプリの基礎」で作成したお絵かきアプリに対して、これらの機能を追加していきましょう。

AssetsLibraryフレームワークでできること

 第5回記事では、「UIImageWriteToSavedPhotosAlbum」関数を用いてアプリ内からフォトアルバムへ写真を追加する方法を解説しました。ここまではiOS 4以前でも可能でしたが、新しく追加されたAssetsLibraryフレームワークを使用すると、以下のことが可能になります。

  • 写真だけでなくビデオなども含めたフォトライブラリ内の全データを取得できる
  • 複数のデータを一度に取得できる
  • データに設定されたExif(付加情報)を取得できる
  • データにExifを設定して保存できる(※上書き保存は不可。必ず新規保存となる)

AssetsLibraryフレームワークのインポート

 それでは、お絵かきアプリのツールバーに新しく検索ボタンを追加し、このボタンを押すと、フォトライブラリ内の全画像データをテーブルに表示する画面を作成していきましょう。

 第5回のお絵かきアプリのプロジェクト/fsmart/articles/iphonesdk05/SampleOekaki.zipをXcodeで開きます。Xcodeのグループとファイルから[Frameworks]を右クリックし、[追加]→[既存のフレームワーク]を選択します。

 表示されたフレームワークの一覧から[AssetsLibrary.framework][ImageIO.framework]の2つを選択し、[追加]をクリックします。

 以上で、プロジェクトに[AssetsLibrary.framework][ImageIO.framework]が追加されました。なお、ImageIOはExifを保存する際にキーとなる定数を保持しているため必要となります。

 次に、グループとファイルから[Other Sources]→「【アプリ名】_Prefix.pch」を開き、AssetsLibraryとImageIOの共通インポートを追加します。

#ifdef __OBJC__
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <AssetsLibrary/AssetsLibrary.h>
#import <ImageIO/ImageIO.h>
#endif
【アプリ名】_Prefix.pch

写真検索ボタンと「グループ選択」画面を作成

写真検索ボタンの追加

 「DetailViewController.m」のviewDidLoadメソッドに検索ボタンを追加します。

#import "PhotoViewController.h"
 
……【省略】……
 
- (void)viewDidLoad {
 
……【省略】……
 
    // 写真検索ボタンの生成
    UIBarButtonItem *searchtBtn = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSearch
                                              target:self
                                              action:@selector(searchPhoto:)]; 
    NSMutableArray *items = [[toolbar items] mutableCopy];
    [items addObject:cameraBtn];
    [items addObject:searchtBtn];
    [items addObject:spaceBtn];
    [items addObject:titleTextButton];
    [items addObject:spaceBtn];
    [items addObject:trashBtn];
    [toolbar setItems:items animated:NO];
    [items release];
    [cameraBtn release];
    [searchtBtn release];
    [spaceBtn release];
    [trashBtn release];
}
    
// フォトライブラリ内の写真を表示する
-(void)searchPhoto:(id)sender {
    PhotoViewController *controller = [[PhotoViewController alloc] initController];
    controller.delegate = self;
    [self presentModalViewController:controller animated:YES];
    [controller release];
}
DetailViewController.m

 searchPhotoメソッドは、写真検索ボタン押下時に呼ばれます。この中では、UINavigaitionControllerを継承したPhotoViewControllerを作成し、モーダルビューとして表示しています。このPhotoViewControllerにフォトライブラリの内容を表示していきます。

コントローラの作成

 次に、「PhotoViewController」をXcodeのグループとファイルに新規作成します。

@protocol PhotoViewControllerDelegate
 
@required
 
// 写真が選択されたときに呼ばれる
- (void)photoSelect:(UIImage *)image;
 
// 写真の選択をキャンセルしたときに呼ばれる
- (void)photoSelectCancel;
@end
 
@interface PhotoViewController : UINavigationController<PhotoViewControllerDelegate> {
    id delegate;
}
 
@property (nonatomic, assign) id delegate;
 
-(id)initController;
 
@end
PhotoViewController.h

 PhotoViewControllerDelegateプロトコルは呼び出し元のDetailViewController、呼び出し先のViewControllerすべてに実装し、「写真の決定」「選択の取消」が行われた際にDetailViewControllerへ処理が戻るようにしていきます。

#import "PhotoViewController.h"
#import "PhotoGroupViewController.h"
 
@implementation PhotoViewController
 
@synthesize delegate;
 
-(id)initController {
    if(self = [super init]) {
        // グループ選択テーブルを表示するViewControllerをルートビューとして生成
        PhotoGroupViewController *controller = [[PhotoGroupViewController alloc] init];
        controller.delegate = self;
        [super initWithRootViewController:controller];
        [controller release];
    }
    return self;
}
 
#pragma mark PhotoViewControllerDelegate Methods
- (void)photoSelect:(UIImage *)image {
    [delegate photoSelect:image];
}
 
- (void)photoSelectCancel {
    [delegate photoSelectCancel];
}
 
#pragma mark Memory management
- (void)dealloc {
    [super dealloc];
}
 
@end
PhotoViewController.m

 initControllerメソッドでは、UINavigationControllerでテーブル表示を行うため、UITableViewControllerの継承クラスをルートビューに設定しています。

グループコントローラの作成

 次に、「PhotoGroupViewController.h」をXcodeのグループとファイルに新規作成します。

#import "PhotoViewController.h"
 
@interface PhotoGroupViewController : UITableViewController<PhotoViewControllerDelegate> {
    id delegate;
    @private
    NSMutableArray *groups;
    ALAssetsLibrary *library;
}
 
@property (nonatomic, assign) id delegate;
 
@end
PhotoGroupViewController.h

 メンバ変数「gorups」は、AssetsLibraryフレームワークで定義されたALAssetsGroupクラスの集合を保持します。ALAssetsGroupクラスはフォトライブラリ内で分けられた1つのグループを表しています。

 メンバ変数「library」は、AssetsLibraryフレームワークで定義されたALAssetsLibraryクラスであり、フォトライブラリを操作するためのクラスとなります。

#import "PhotoGroupViewController.h"
 
@implementation PhotoGroupViewController
 
@synthesize delegate;
 
#pragma mark Rotation support
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    return YES;
}
 
#pragma mark View lifecycle
- (void)viewDidLoad {
    [super viewDidLoad];
    [self.navigationItem setTitle:@"グループ選択"];
 
    // 別スレッドでフォトライブラリ内のグループを検索
    [self performSelectorInBackground:@selector(searchPhotoGroups) withObject:nil];	
 
    // 写真選択のキャンセルボタンを生成
    UIBarButtonItem *cancelButton = [
        [UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
                                                     target:self
                                                     action:@selector(photoSelectCancel)
    ];
    [self.navigationItem setRightBarButtonItem:cancelButton];
    [cancelButton release];
}
 
-(void)searchPhotoGroups {
    groups = [[NSMutableArray alloc] init];
    library = [[ALAssetsLibrary alloc] init];
 
    // フォトライブラリから取得したグループをgroupsに追加。追加するたびに画面のリロードを行う
    void (^groupBlock)(struct ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop) {
    if(group != nil)
        [groups addObject:group];
        [self performSelectorOnMainThread:@selector(reload) withObject:nil waitUntilDone:NO];
    };
 
    // フォトライブラリへアクセスし、引数のブロックを実行する。
    [library enumerateGroupsWithTypes:ALAssetsGroupAll
    usingBlock:groupBlock
    failureBlock:^(NSError *error) {
        NSLog(@"A problem occured");
    }];
}
 
-(void)reload {
    [self.tableView reloadData];
}
 
#pragma mark Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}
 
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [groups count];
}
 
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {    
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil)
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault 
                                       reuseIdentifier:CellIdentifier] autorelease];
 
    // フォトライブラリ内のポスター画像・グループ名・データ数を取得し、セルに表示する
    UIImage *image = [UIImage imageWithCGImage:
        [(ALAssetsGroup*)[groups objectAtIndex:indexPath.row] posterImage]];
    NSString *groupName = [(ALAsset*)[groups objectAtIndex:indexPath.row] 
                                          valueForProperty:ALAssetsGroupPropertyName];
    NSInteger photoCnt = [(ALAssetsGroup*)[groups objectAtIndex:indexPath.row] numberOfAssets];
    cell.imageView.image = image;
    cell.textLabel.text = [NSString stringWithFormat:@"%@ (%d)", groupName, photoCnt];
    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    return cell;
}
 
#pragma mark Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
}
 
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return 80;
}
#pragma mark PhotoViewControllerDelegate Methods
- (void)photoSelect:(UIImage *)image {
    [delegate photoSelect:image];
}
 
- (void)photoSelectCancel {
    [delegate photoSelectCancel];
}
    
#pragma mark Memory management
- (void)dealloc {
    [groups release];
    [library release];
    [super dealloc];
}
    
@end
PhotoGroupViewController.m

 「viewDidLoadメソッド」では、performSelectorInBackgroundメソッドを使用して別スレッドでフォトライブラリ内の検索を行っています。別スレッドにしておくことで大量の画像データを検索する場合も画面がスムーズに表示されます。

 「searchPhotoGroups」メソッドでは、「groupBlock」というブロック構文をALAssetsLibraryのenumerateGroupWithTypesメソッドに渡すことでフォトライブラリ内のグループデータをgroupsに追加しています。

 Objective-C言語のブロック構文については、アップル公式ドキュメント「ブロックプログラミングトピック」をご参照ください。

 「tableView : cellForRowAtIndexPath : indexPath」メソッドでは、メンバ変数gorupsからテーブル行ごとのALAssetGroupを取得し、グループに設定されたポスター画像・グループ名・データ数を取得し、セルにセットしています。

いったん実行して確認

 ここまで実装が完了したら、ビルドして実行([Command]+[R]キー)してみましょう。カメラボタンの右側に作った検索ボタンを押すと、「グループ選択」画面が現れ、フォトライブラリ内のグループが表示されます。

 次ページでは、このグループの中から写真を選択するための一覧画面を作成します。

Copyright © ITmedia, Inc. All Rights Reserved.

編集部からのお知らせ

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。