ALAssetsLibraryについて
以下は私がWebや書籍から集めた情報や、私が試行錯誤した経験により、ALAssetsLibraryについてまとめたものです。
そのため、間違いが含まれている可能性があります。何かしらの間違いを見つけた方はご指摘願います。
はじめに
ALAssetsLibraryとはiOS4で追加された、iPhone/iPad/iPodの写真/映像フォルダにアクセスするためのフレームワークです。
私はiOS4がリリースされた頃からiOSプログラミングを始めたため、iOS3以前については良く知りませんが、iOS3以前ではファイルパスを指定し、写真/映像フォルダに直接アクセスしていたようです。しかし現在はファイルに直接アクセスするとAppleの審査が通らないようです。そのため現在iPhone/iPad/iPodの写真・映像フォルダにアクセスするためにはALAssetsLibraryを使用しないといけません。
ALAssetsLibraryを使用すると画像データのEXIFデータなども取得でき、便利です。
ALAssetsLibraryがリリースされてから半年程度経ちますが、ALAssetsLibraryに関するサンプルコードやチュートリアルなどはかなり少ないです。そのため今回ALAssetsLibraryについてまとめることにしました。
サンプルコード
MYAssetsAccessor.h
#import <UIKit/UIKit.h> @class ALAssetsLibrary; @interface MYAssetsAccessor : NSObject { @private ALAssetsLibrary *assetsLibrary_; } - (void)loadUrlsWithCallbackTarget:(id)anObject callbackMethod:(SEL)aSelector; - (NSDictionary *)metadataOfAssetIdentifiedByUrl:(NSURL *)url; - (UIImage *)thumbnailOfAssetsIdentifiedByUrl:(NSURL *)url; - (UIImage *)fullResolutionImageOfAssetsIdentifiedByUrl:(NSURL *)url; + (void)addObserverForAssetsLibraryChange:(id)anObserver selector:(SEL)aSelector; + (void)removeObserverForAssetsLibraryChange:(id)anObserver; // to understand how the enumeration works. - (void)enumerationTest_; @end
MYAssetsAccessor.m
#import <AssetsLibrary/AssetsLibrary.h> // require AssetsLibrary.framework #import "MYAssetsAccessor.h" @implementation MYAssetsAccessor - (id)init { assetsLibrary_ = [[ALAssetsLibrary alloc] init]; return self; } - (void)dealloc { [assetsLibrary_ release]; [super dealloc]; } - (void)loadUrlsWithCallbackTarget:(id)anObject callbackMethod:(SEL)aSelector { id callbackTarget = [anObject retain]; SEL callbackMethod = aSelector; NSMutableArray *urls = [[NSMutableArray alloc] init]; void (^assetResultBlock)(ALAsset *, NSUInteger, BOOL *) = ^(ALAsset *asset, NSUInteger index, BOOL *stop) { if (!*stop && asset) { ALAssetRepresentation *defaultRepresentation = [asset defaultRepresentation]; [urls addObject:[defaultRepresentation url]]; } }; void (^resultBlock)(ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop) { if (*stop || !group) { [callbackTarget performSelector:callbackMethod withObject:urls]; } else { [group setAssetsFilter:[ALAssetsFilter allAssets]]; [group enumerateAssetsUsingBlock:assetResultBlock]; } }; void (^failureBlock)(NSError *) = ^(NSError *error) { NSLog(@"exception in enumerating assets. %@", error); [callbackTarget performSelector:callbackMethod withObject:urls]; }; ALAssetsGroupType targetGroupType = ALAssetsGroupAll; [assetsLibrary_ enumerateGroupsWithTypes:targetGroupType usingBlock:resultBlock failureBlock:failureBlock]; [urls release]; [callbackTarget release]; } - (NSDictionary *)metadataOfAssetIdentifiedByUrl:(NSURL *)url { __block NSDictionary *metadata = nil; [assetsLibrary_ assetForURL:url resultBlock:^(ALAsset *asset) { ALAssetRepresentation *defaultRepresentation = [asset defaultRepresentation]; metadata = [[defaultRepresentation metadata] copy]; } failureBlock:^(NSError *error) { NSLog(@"exception in accessing assets by url. %@", error); }]; return [metadata autorelease]; } - (UIImage *)thumbnailOfAssetsIdentifiedByUrl:(NSURL *)url { __block UIImage *thumbnail = nil; [assetsLibrary_ assetForURL:url resultBlock:^(ALAsset *asset) { thumbnail = [[UIImage alloc] initWithCGImage:[asset thumbnail]]; } failureBlock:^(NSError *error) { NSLog(@"exception in accessing assets by url. %@", error); }]; return [thumbnail autorelease]; } - (UIImage *)fullResolutionImageOfAssetsIdentifiedByUrl:(NSURL *)url { __block UIImage *fullResolutionImage = nil; [assetsLibrary_ assetForURL:url resultBlock:^(ALAsset *asset) { ALAssetRepresentation *defaultRepresentation = [asset defaultRepresentation]; fullResolutionImage = [[UIImage alloc] initWithCGImage:[defaultRepresentation fullResolutionImage]]; } failureBlock:^(NSError *error) { NSLog(@"exception in accessing assets by url. %@", error); }]; return [fullResolutionImage autorelease]; } + (void)addObserverForAssetsLibraryChange:(id)anObserver selector:(SEL)aSelector { NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter]; [defaultCenter addObserver:anObserver selector:aSelector name:ALAssetsLibraryChangedNotification object:nil]; } + (void)removeObserverForAssetsLibraryChange:(id)anObserver { NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter]; [defaultCenter removeObserver:anObserver name:ALAssetsLibraryChangedNotification object:nil]; } // to understand how the enumeration works. - (void)enumerationTest_ { void (^assetResultBlock)(ALAsset *, NSUInteger, BOOL *) = ^(ALAsset *asset, NSUInteger index, BOOL *stop) { if (*stop) { NSLog(@"stopped enumerating assets."); } else if (!asset) { NSLog(@"the end of the group."); } else { NSDictionary *urls = [asset valueForProperty:ALAssetPropertyURLs]; NSLog(@"%@", urls); } }; void (^resultBlock)(ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop) { if (*stop) { NSLog(@"stopped enumerating groups."); } else if (!group) { NSLog(@"the end of enumation."); } else { NSString *groupName = [group valueForProperty:ALAssetsGroupPropertyName]; ALAssetsFilter *photosFilter = [ALAssetsFilter allPhotos]; [group setAssetsFilter:photosFilter]; NSInteger numberOfPhotos = [group numberOfAssets]; NSLog(@"%d photos in %@", numberOfPhotos, groupName); [group enumerateAssetsUsingBlock:assetResultBlock]; ALAssetsFilter *videosFilter = [ALAssetsFilter allVideos]; [group setAssetsFilter:videosFilter]; NSInteger numberOfVideos = [group numberOfAssets]; NSLog(@"%d videos in %@", numberOfVideos, groupName); [group enumerateAssetsUsingBlock:assetResultBlock]; ALAssetsFilter *allFilter = [ALAssetsFilter allAssets]; [group setAssetsFilter:allFilter]; NSInteger numberOfAssets = [group numberOfAssets]; NSLog(@"%d assets in %@", numberOfAssets, groupName); [group enumerateAssetsUsingBlock:assetResultBlock]; } }; void (^failureBlock)(NSError *) = ^(NSError *error) { NSLog(@"exception in enumerating assets. %@", error); }; // ALAssetsGroupType targetGroupType = ALAssetsGroupLibrary | // ALAssetsGroupAlbum | // ALAssetsGroupEvent | // ALAssetsGroupFaces | // ALAssetsGroupSavedPhotos; ALAssetsGroupType targetGroupType = ALAssetsGroupAll; [assetsLibrary_ enumerateGroupsWithTypes:targetGroupType usingBlock:resultBlock failureBlock:failureBlock]; // the result will be: // 2011-04-28 18:38:40.939 MyProject[869:707] 4 photos in Photo Library // 2011-04-28 18:38:40.964 MyProject[869:707] { // "public.jpeg" = "assets-library://asset/asset.jpg?id=104&ext=jpg"; // } // ... // 2011-04-28 18:38:40.989 MyProject[869:707] the end of the group. // 2011-04-28 18:38:40.993 MyProject[869:707] 0 videos in Photo Library // 2011-04-28 18:38:40.997 MyProject[869:707] the end of the group. // 2011-04-28 18:38:41.003 MyProject[869:707] 4 assets in Photo Library // 2011-04-28 18:38:41.008 MyProject[869:707] { // "public.jpeg" = "assets-library://asset/asset.jpg?id=104&ext=jpg"; // } // ... // 2011-04-28 18:38:41.030 MyProject[869:707] the end of the group. // 2011-04-28 18:38:41.039 MyProject[869:707] 25 photos in Camera Roll // 2011-04-28 18:38:41.053 MyProject[869:707] { // "public.jpeg" = "assets-library://asset/asset.JPG?id=1000000021&ext=JPG"; // } // ... // 2011-04-28 18:38:41.273 MyProject[869:707] the end of the group. // 2011-04-28 18:38:41.277 MyProject[869:707] 4 videos in Camera Roll // 2011-04-28 18:38:41.285 MyProject[869:707] { // "com.apple.quicktime-movie" = "assets-library://asset/asset.MOV?id=1000000004&ext=MOV"; // } // ... // 2011-04-28 18:38:41.314 MyProject[869:707] the end of the group. // 2011-04-28 18:38:41.319 MyProject[869:707] 29 assets in Camera Roll // 2011-04-28 18:38:41.327 MyProject[869:707] { // "public.jpeg" = "assets-library://asset/asset.JPG?id=1000000021&ext=JPG"; // } // ... // 2011-04-28 18:38:41.569 MyProject[869:707] the end of the group. // 2011-04-28 18:38:41.573 MyProject[869:707] the end of enumation. } @end
AppDelegate_iPhone.h
#import <UIKit/UIKit.h> @class MYAssetsAccessor; @interface AppDelegate_iPhone : NSObject <UIApplicationDelegate> { UIWindow *window_; MYAssetsAccessor *myAssetsAccessor_; } @property (nonatomic, retain) IBOutlet UIWindow *window; @end
AppDelegate_iPhone.m
#import "AppDelegate_iPhone.h" #import "MYAssetsAccessor.h" @interface AppDelegate_iPhone(Private) - (void)assetLibraryDidChange_:(NSNotification *)aNotification; - (void)assetUrlsDidLoad_:(NSArray *)urls; @end @implementation AppDelegate_iPhone @synthesize window = window_; - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [self.window makeKeyAndVisible]; myAssetsAccessor_ = [[MYAssetsAccessor alloc] init]; [MYAssetsAccessor addObserverForAssetsLibraryChange:self selector:@selector(assetLibraryDidChange_:)]; [myAssetsAccessor_ enumerationTest_]; [myAssetsAccessor_ loadUrlsWithCallbackTarget:self callbackMethod:@selector(assetUrlsDidLoad_:)]; return YES; } - (void)applicationWillTerminate:(UIApplication *)application { [MYAssetsAccessor removeObserverForAssetsLibraryChange:self]; } - (void)dealloc { [myAssetsAccessor_ release]; [window_ release]; [super dealloc]; } @end @implementation AppDelegate_iPhone(Private) - (void)assetLibraryDidChange_:(NSNotification *)aNotification { NSLog(@"asset library changed. %@", aNotification); // TODO reload urls } - (void)assetUrlsDidLoad_:(NSArray *)urls { NSLog(@"%d urls, %@", [urls count], urls); if ([urls count] == 0) { return; } NSURL *url = [urls objectAtIndex:30]; NSDictionary *metadata = [myAssetsAccessor_ metadataOfAssetIdentifiedByUrl:url]; NSLog(@"metadata of %@ is %@", url, metadata); UIImage *thumbnail = [myAssetsAccessor_ thumbnailOfAssetsIdentifiedByUrl:url]; NSLog(@"thumbnail of %@ is %@", url, thumbnail); UIImage *fullResolutionImage = [myAssetsAccessor_ fullResolutionImageOfAssetsIdentifiedByUrl:url]; NSLog(@"full resolution image of %@ is %@", url, fullResolutionImage); } @end
サンプルコード概要
MYAssetsAccessorはALAssetsLibraryを利用した、画像/映像フォルダへアクセスするためのクラスです。AppDelegate_iPhoneはMYAssetsAccessorを利用したコードです。
MYAssetsAccessorのloadUrlsWithCallbackTarget:callbackMethod:で画像/映像フォルダ中の画像/映像を表すURLの配列を取得します。メソッド中のenumerateGroupsWithTypes:usingBlock:failureBlock:は非同期で実行されるため、URLの取得が終了するもしくは何らかのエラーが生じた場合、指定したcallbackメソッドを呼び出します。なお、ブロック内で*stop = YES;とすると、走査を終了させることもできます。
URLの一覧が取得できれば後はメタデータや、サムネイルを取得したりといろいろできます。
URLは画像/映像フォルダに何らかの変更が生じた場合、変更される事があるので、ALAssetsLibraryChangeNotificationを監視しておきます。
MYAssetsAccessorには動作確認用のenumerationTest_メソッドを用意しています。画像/映像フォルダを走査して、URL等をログに書き出します。
サンプルコードはMITライセンスです。
MYAssetsAccessor.h
MYAssetsAccessor.m
AppDelegate_iPhone.h
AppDelegate_iPhone.m
注意点
ALAssetsLibraryのインスタンスのメソッドはメインスレッドで呼ぶ方が良い
ALAssetsLibraryのインスタンスのメソッドを初めて呼び出すと、次のようなアラートが表示されます。
メインスレッドで呼び出した場合は上記のアラートが表示されます。
しかし、メインスレッド以外で呼び出した場合は上記のアラートが表示されず、ユーザの入力待ち状態のままになり、ALAssetsLibraryに関する処理が一向に進まなくなります。
なお、上記のアラートでユーザがDon't Allowを選択すると写真フォルダにアクセスできず、failureBlockが呼び出されます。
ALAssetsLibraryのインスタンスのメモリ消費量は写真/映像フォルダ内のコンテンツ量に応じて増える
画像フォルダに10枚しか写真がない場合と、画像フォルダに10,000枚の写真がある場合とでは、ALAssetsLibraryのインスタンスのメモリ消費量がかなり違います。検索用インデックスでも作っているのでしょうか。
画像/映像フォルダ内のコンテンツ量が増えるにつれ、アプリケーションで使用できるメモリ量が少なくなってしまいます。動作保障コンテンツ数などを決める必要があるのかもしれません。iPhone/iPad/iPodのデフォルトの写真ビューアも画像が80,000枚程度保存されていると強制終了するようになりますし。
ALAssetsLibraryChangeNotificationはバックグラウンド状態では受信できない
1. 対象のアプリケーションをバックグラウンド状態にする
2. 他のアプリケーションを起動/フォアグラウンド状態にする
3. 他のアプリケーションで画像/映像フォルダに変更が生じる(新たな画像を追加する、など)
4. 他のアプリケーションを終了/バックグラウンド状態にする
5. 対象のアプリケーションをフォアグラウンド状態にする
上記のようなアプリケーションの切り替えなどを行った場合、対象のアプリケーションがALAssetsLibraryChangeNotificationを受信するのは5のタイミングです。3の画像/映像フォルダに変更が生じた際ではなく、5の対象のアプリケーションがフォアグラウンド状態になったときです。
iTunesの同期ではALAssetsLibraryChangeNotificationは発生しない
iTunesの同期により画像/映像フォルダに変更が生じても、ALAssetsLibraryChangeNotificationは発生しません。
iTunesで同期すると画像のEXIFは消失する
PCからiTunes経由でiPhone/iPad/iPodに画像を転送した場合、転送元のPC上の画像にEXIFが付属していたとしても、iPhone/iPad/iPodに転送される際に削除されるようです。
下記参考文献のiOS4プログラミングブックの中でも次のように述べられています。
PC上のオリジナル画像がExif情報付きだったとしても、同期するタイミングで情報が削られているようです。
ただし、iPhone/iPad/iPodのカメラで撮った写真に関しては、iTuneの同期でEXIFは削除されません。おそらくApple独自のメタデータ形式などがあるのでしょう。
iTunesで同期すると画像はJpegとして保存される
ALAssetsLibraryと直接関係はないですが、iTunesで同期すると、bmpもpngもjpegに変換されてiPhone/iPad/iPod上に保存されるようです。
画像/映像の読み込みと新規書き込みのみ可能
ALAssetsLibraryでは、画像/映像の読み込みと新規書き込みのみが可能で、削除や修正/上書きなどはできないようです。
参考
公式リファレンス
ALAssetsLibrary Class Reference
Apple公式のリファレンスです。
読み込み
iOS 4の新機能13選&AssetsLibraryで作る画像ビューア (2/4):SDKで始めるiPad/iPhoneアプリ開発の勘所(終) - @IT
ALAssetsLibraryを使用した画像ビューアの作成方法。ソースコードのダウンロードもできます。
iPhoneの画像からEXIF,GPSデータを取得する ~ guess what?
GPSデータの読み込みなど。
書き込み
[AssetsLibrary] フォトライブラリの写真にExif情報を付けて保存する - Ni chicha, ni limona - 平均から抜けられない僕 - iPhoneアプリ開発グループ
画像データをEXIF付きで保存する。
書籍
- 作者: 畑圭輔,加藤寛人,坂本一樹,藤川宏之,高橋啓治郎,沖田知彦,柳澤昇
- 出版社/メーカー: インプレス
- 発売日: 2011/01/27
- メディア: 単行本(ソフトカバー)
- 購入: 26人 クリック: 910回
- この商品を含むブログ (33件) を見る
私が知っている限り、ALAssetsLibraryについて書かれている日本語の本はこれだけです。