ALAssetLibrary通知和enumerateAssetsUsingBlock
我正在构build一个iOS应用程序,允许用户拍摄照片并将其保存到自定义相册中,该应用程序使用相册中的照片在应用程序中填充UICollectionView。 我在网上find了很多关于如何做到这一点的例子,但是我不能在最近的一张专辑中没有显示出来的问题。 我结束了使用通知和串行调度队列的工作,但是我认为可以改进。
以下是我如何保存图片,然后重新填充UICollectionView:
我build立了一个方法来调用,当我需要枚举相册(viewDidLoad,一旦我收到了ALAssetsLibraryChangedNotification)。
-(void)setupCollectionView { _assets = [@[] mutableCopy]; __block NSMutableArray *tmpAssets = [@[] mutableCopy]; // This grabs a static instance of the ALAssetsLibrary ALAssetsLibrary *assetsLibrary = [ProfileViewController defaultAssetsLibrary]; // Enumerate through all of the ALAssets (photos) in the user's Asset Groups (Folders) [assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop) { if ([[group valueForProperty:ALAssetsGroupPropertyName] isEqualToString:albumName]) { // Make sure we have the group... (this may be redundant) if (group == nil){ return; } // I read in some SO posts that setting the filter might help, but I don't think it does NSLog(@"Refresh pictures - %d",[group numberOfAssets]); [group setAssetsFilter:[ALAssetsFilter allPhotos]];//this will cause group to reload if (group!=nil) { [group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) { if(result){ [tmpAssets addObject:result]; }else{ *stop = YES; } }]; } self.assets = tmpAssets; // Reload the UICollectionView dispatch_async(dispatch_get_main_queue(), ^{ [self.collectionView reloadData]; [self.collectionView.collectionViewLayout invalidateLayout]; }); } } failureBlock:^(NSError *error) { NSLog(@"Error loading images %@", error); }]; }
起初,这似乎工作得很好,但偶尔,在将图像写入自定义相册后调用此function时,刚拍摄的照片将不会显示在collections视图中。 照片将被保存到图库(它出现在照片应用程序),退出并重新运行应用程序后,照片将出现,但枚举将不包括最新的照片。
正如在这个问题上的一些堆栈溢出post所build议的,我尝试通过侦听ALAssetsLibraryChangedNotification来实现ALAssets的通知,但是我遇到了一些问题。 一旦我收到通知,以下是我正在做的事情:
- (void) handleAssetChangedNotifiation:(NSNotification *)notification { NSDictionary *userInfo = notification.userInfo; NSSet *updateAssets = userInfo[ALAssetLibraryUpdatedAssetsKey]; if (updateAssets.count>0) { dispatch_sync(photoLoadQueue, ^{ [self setupCollectionView]; }); } }
这对我来说似乎很好。 我正在检查ALAssetLibraryUpdatedAssetsKey中是否有任何数据,然后将其投入到调度队列中,一旦我发现我正在接收多个保存单个照片的通知,我就开始使用它。 这里是我给自定义库写照片后收到的通知:
Received notification: NSConcreteNotification 0x15ed4af0 {name = ALAssetsLibraryChangedNotification; object = <ALAssetsLibrary: 0x15db9e80>; userInfo = { ALAssetLibraryUpdatedAssetGroupsKey = "{(\n assets-library://group/?id=43E80EF8-EA91-4C71-AFD2-EBDCB53A1D53\n)}"; ALAssetLibraryUpdatedAssetsKey = "{(\n assets-library://asset/asset.JPG?id=63E43AF3-3729-43E9-806C-BE463116FDA2&ext=JPG,\n assets-library://asset/asset.JPG?id=FA2ED24E-DF0F-49A3-85B8-9C181E4077A4&ext=JPG,\n assets-library://asset/asset.JPG?id=A0B66E2E-6EA1-462C-AD93-DB3087BA65D8&ext=JPG\n)}";}} Received notification: NSConcreteNotification 0x15d538c0 {name = ALAssetsLibraryChangedNotification; object = <ALAssetsLibrary: 0x15db9e80>; userInfo = { ALAssetLibraryUpdatedAssetsKey = "{(\n assets-library://asset/asset.JPG?id=FBDD2086-EC76-473F-A23E-4F4200C0A6DF&ext=JPG\n)}";}} Received notification: NSConcreteNotification 0x15dc9230 {name = ALAssetsLibraryChangedNotification; object = <ALAssetsLibrary: 0x15db9e80>; userInfo = {}} Received notification: NSConcreteNotification 0x15df5b40 {name = ALAssetsLibraryChangedNotification; object = <ALAssetsLibrary: 0x15db9e80>; userInfo = {}} Received notification: NSConcreteNotification 0x15d6a050 {name = ALAssetsLibraryChangedNotification; object = <ALAssetsLibrary: 0x15db9e80>; userInfo = {}} Received notification: NSConcreteNotification 0x15d379e0 {name = ALAssetsLibraryChangedNotification; object = <ALAssetsLibrary: 0x15db9e80>; userInfo = { ALAssetLibraryUpdatedAssetGroupsKey = "{(\n assets-library://group/?id=1E76AFA7-89B4-4277-A175-D7C8E62E49D0&filter=1,\n assets-library://group/?id=1E76AFA7-89B4-4277-A175-D7C8E62E49D0\n)}"; ALAssetLibraryUpdatedAssetsKey = "{(\n assets-library://asset/asset.JPG?id=FBDD2086-EC76-473F-A23E-4F4200C0A6DF&ext=JPG\n)}"; Received notification: NSConcreteNotification 0x15df7bf0 {name = ALAssetsLibraryChangedNotification; object = <ALAssetsLibrary: 0x15db9e80>; userInfo = { ALAssetLibraryUpdatedAssetGroupsKey = "{(\n assets-library://group/?id=8E390051-E107-42BC-AE55-24BA35966642\n)}";}} Received notification: NSConcreteNotification 0x15d40360 {name = ALAssetsLibraryChangedNotification; object = <ALAssetsLibrary: 0x15db9e80>; userInfo = { ALAssetLibraryUpdatedAssetsKey = "{(\n assets-library://asset/asset.JPG?id=5A45779A-C3C1-4545-9BD6-88F094FFA5B9&ext=JPG\n)}";}}
为什么我收到这么多通知? 我没有看到一种方法来查找一个特定的消息,告诉我一旦图像准备就绪,所以我使用该调度队列连续发射枚举。 有没有人有任何这方面的经验或解决scheme? 我的工作,但我肯定比我应该做更多的工作。
只是为了完成,这里是我保存图像的方法(我可能不应该像我这样嵌套这些块,但我这样做,以尝试修复添加的图像不被检测到):
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { UIImage *originalImage, *editedImage, *imageToSave; editedImage = (UIImage *) [info objectForKey:UIImagePickerControllerEditedImage]; originalImage = (UIImage *) [info objectForKey:UIImagePickerControllerOriginalImage]; if (editedImage) { imageToSave = editedImage; }else { imageToSave = originalImage; } // Get group/asset library/album __block ALAssetsGroup* groupToAddTo; ALAssetsLibrary *assetsLibrary = [ProfileViewController defaultAssetsLibrary]; [[NSNotificationCenter defaultCenter] removeObserver:self name:ALAssetsLibraryChangedNotification object:nil]; [assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop) { if ([[group valueForProperty:ALAssetsGroupPropertyName] isEqualToString:albumName]) { NSLog(@"found album %@", albumName); groupToAddTo = group; // save to album CGImageRef img = [imageToSave CGImage]; // There's some orientation stuff here I pulled out for brevity's sake [assetsLibrary writeImageToSavedPhotosAlbum:img orientation: alOrientation completionBlock:^(NSURL* assetURL, NSError* error) { if (error.code == 0) { [assetsLibrary assetForURL:assetURL resultBlock:^(ALAsset *asset) { // assign the photo to the album if ([groupToAddTo valueForProperty:ALAssetsGroupPropertyURL] == nil){ NSLog(@"group properties are nil!"); }else { if([groupToAddTo addAsset:asset]){ // If the asset writes to the library, listen for the notification [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAssetChangedNotifiation:) name:ALAssetsLibraryChangedNotification object:nil]; }else { NSLog(@"Image Not Added!"); } } } failureBlock:^(NSError* error) { NSLog(@"failed to retrieve image asset:\nError: %@ ", [error localizedDescription]); }]; }else { NSLog(@"saved image failed.\nerror code %i\n%@", error.code, [error localizedDescription]); } }]; }} failureBlock:^(NSError* error) { NSLog(@"failed to enumerate albums:\nError: %@", [error localizedDescription]);}]; [picker dismissViewControllerAnimated:YES completion:NULL]; }
需要认识到的是与ALAssetsLibrary相关的块是asynchronous的 。 因此,这样的代码不起作用:
if (group!=nil) { [group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) { if(result){ [tmpAssets addObject:result]; }else{ *stop = YES; } }]; } self.assets = tmpAssets;
“最后一行” self.assets = tmpAssets
在块之前运行,因此tmpAssets
从来没有机会正确设置。
与ALAssetsLibrary调用相关的所有块都是同样的事情。
一旦你掌握了这个,你可以重构你的代码,使所有事情都按照正确的顺序进行。 事实上,事实certificate, 这是通过枚举块的额外传递 。 正如我在书中写的:
我最初被这个好奇的块枚举行为迷惑了,但总有一天它的原因就闪现在这里:这些块都是asynchronous调用的(在主线程上),这意味着你的其他代码已经完成了运行,所以你可以在第一次获得通过这个块的额外通行证,这是你第一次有机会用你以前通过的所有数据进行处理。
所以你看,你想要你的代码结构更像这样:
if (group!=nil) { [group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) { if(result){ [tmpAssets addObject:result]; }else{ self.assets = tmpAssets; // <--- !!!! // and now proceed to whatever the _next_ step is...! } }]; }
所以,你看,你完全走错了路。 摆脱所有的通知,并根据这些新知识彻底重构所有代码。 一旦你理解了这个简单但logging不完善的事实,ALAssetsLibrary是完全一致的。