ALAssetLibrary通知和enumerateAssetsUsingBlock [英] ALAssetLibrary Notification and enumerateAssetsUsingBlock

查看:203
本文介绍了ALAssetLibrary通知和enumerateAssetsUsingBlock的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在构建一个iOS应用程序,该应用程序允许用户拍摄照片并将其保存到自定义相册中,然后使用该相册中的照片填充该应用程序中的UICollectionView.我在网络上找到了许多有关如何执行此操作的示例,但是我无法克服一个棘手的问题,因为最近的照片没有出现在相册中.我结束了围绕使用通知和串行调度队列的工作,但是我认为它可以改进.

I'm building an iOS app that allows a user to take a picture and save it to a custom album, which populates a UICollectionView in the app with the photos from the album. I found plenty of examples on the web on how to do this, but I could NOT get past a stubborn problem with the most recent picture not showing up in the album. I ended building a work around using notifications and a serial dispatch queue, but I think it can be improved.

这是我保存图片然后重新填充UICollectionView的方式:

Here's how I'm saving the pictures and then repopulating the UICollectionView:

当需要枚举相册(viewDidLoad,并且收到ALAssetsLibraryChangedNotification)后,我构建了一种调用方法.

I built a method to call when I need to enumerate the photo album (viewDidLoad and once I've received a 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);
    }];
}

最初,这似乎工作得很好,但是有时,在将图像写入自定义相册后调用此功能时,刚拍摄的照片不会出现在收藏夹视图中.该照片将被保存到图库(显示在照片"应用程序中),并且在退出并重新运行该应用程序后,将显示该照片,但枚举将不包括该最新照片.

Initially this seemed to be working just fine, but occasionally, when calling this after writing an image to the custom photo album, the photo that was just taken would not appear in the collection view. The photo would be saved to the library (it shows up in the Photos app) and after exiting and re-running the app the photo would appear, but the enumeration would not include that latest photo.

正如有关此问题的一些Stack Overflow帖子所建议的那样,我尝试通过侦听ALAssetsLibraryChangedNotification来实现ALAssets的通知,但是我遇到了一些问题.收到通知后,我正在做的事情是

As suggested by a few Stack Overflow posts on the matter, I tried implementing ALAssets' notifications by listening for ALAssetsLibraryChangedNotification, but I've had some issues with that. Here's what I'm doing once I receive a notification:

- (void) handleAssetChangedNotifiation:(NSNotification *)notification {
    NSDictionary *userInfo = notification.userInfo;
    NSSet *updateAssets = userInfo[ALAssetLibraryUpdatedAssetsKey];
    if (updateAssets.count>0) {        
        dispatch_sync(photoLoadQueue, ^{
            [self setupCollectionView];
        });
    }
}

这对我来说似乎很好.我正在检查ALAssetLibraryUpdatedAssetsKey中是否有任何数据,然后将其扔到调度队列中,一旦发现接收到保存单张照片的几条通知,便开始使用该队列.将照片写到自定义库后,我将收到以下通知:

This seems fine to me. I'm checking to see if there's any data in the ALAssetLibraryUpdatedAssetsKey and then throwing it on the dispatch queue, which I'm started using once I discovered I was receiving several notifications for saving a single photo. Here're the notifications I'm receiving once I write a photo to the custom library:

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)}";}}

为什么我会收到这么多通知?我没有找到一种方法来查找一条特定的消息,该消息会在图像准备好后告诉我,因此我使用该分派队列顺序触发了枚举.有没有人对此有任何经验或解决方案?我的工作正常,但我的工作肯定比我应该做的要多.

Why am I getting so many notifications? I don't see a way to look for one particular message that will tell me once the image is ready, so I used that dispatch queue to serially fire off the enumeration. Does anyone have any experience with this or a solution? Mine works, but I'm definitely doing more work than I should.

为了完整起见,这是我保存图像的方法(我可能不应该像我一样嵌套这些块,但是我这样做是为了尝试修复未检测到的添加图像):

Just to be complete, here's the method where I'm saving the image (I probably shouldn't nest these blocks like I have, but I did this to try and fix the added image not being detected):

- (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关联的块是异步.因此,这样的代码行不通:

The thing to realize is that the blocks associated with ALAssetsLibrary are asynchronous. Thus, code like this doesn't work:

        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永远没有机会正确设置.

The "last" line, self.assets = tmpAssets, runs before the block - and thus tmpAssets never has a chance to be set properly.

对于与ALAssetsLibrary调用关联的 all 块,也是如此.

The same sort of thing is true for all blocks associated with ALAssetsLibrary calls.

一旦掌握了这一点,就可以重组代码,以便一切按正确的顺序进行.实际上,事实证明这是通过枚举块的额外遍历的目的.当我在书中写道:

Once you grasp this you can restructure your code so that everything happens in the correct order. In fact, it turns out that this is what the extra pass through the enumerating blocks is for. As I write in my book:

最初,我对这种奇怪的块枚举行为感到迷惑,但是有一天我突然想到了它的原因:这些块都被异步地称为 (在主线程上),这意味着您其余的代码已经完成运行,因此您将获得一次额外的遍历,这是您第一次使用您在先前遍历中收集的所有数据进行 操作的机会.

I was initially mystified by this curious block enumeration behavior, but one day the reason for it came to me in a flash: these blocks are all called asynchronously (on the main thread), meaning that the rest of your code has already finished running, so you're given an extra pass through the block as your first opportunity to do something with all the data you've presumably gathered in the previous passes.

因此,您看到了,您希望代码的结构更像这样:

So you see, you want your code to be structured more like this:

        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...!
                }
            }];
        }

因此,您看到,您完全走错了路.摆脱掉所有的通知,并根据新知识完全重组所有代码.一旦您了解了有关其工作原理的一个简单但文献记载不多的事实,ALAssetsLibrary就完全一致了.

So, you see, you've totally gone down the wrong road. Get rid of all your notifications and restructure all your code completely in the light of this new knowledge. ALAssetsLibrary is completely coherent once you understand this simple but poorly documented fact about how it works.

这篇关于ALAssetLibrary通知和enumerateAssetsUsingBlock的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆