0

I'm trying to save a list of assets to upload in a sqllite3 db, but when i parse the database and set the assets to an array, then try to use the asset i get a SIGABRT error.

ALAsset *asset = (ALAsset *) assets[indexPath.row];
cell.textLabel.text = [NSString stringWithFormat:@"image%d: ready to upload.",indexPath.row];
cell.detailTextLabel.text = @"1.3MB to folder <server folder>";
[[cell imageView] setImage:[UIImage imageWithCGImage:[asset thumbnail]]];// SIGABRT ERROR

Im saving the ALAsset to the database as a string (TEXT) with UTF8formatting

NSMutableArray *tmpArray = [NSMutableArray alloc]init];
///get sql
[tmpArray addObject:someStringFromSQL];
///end sql loop

assets = [tmpArray mutableCopy];

in the code above I tried:

 [[cell imageView] setImage:[UIImage imageWithCGImage:[(ALAsset *) asset thumbnail]]];// SIGABRT ERROR

and that didn't work.

This is the error:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFString thumbnail]: unrecognized selector sent to instance 0xc0a7800'

Any suggestions?

Also as a side question: Does anyone know how to get the file size (i.e. 1.3MB) from the asset?

BLOCK:

 -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
//do stuff in cell

    NSURL *aURL =[NSURL URLWithString:[assets objectAtIndex:indexPath.row]];
    [assetsLibrary assetForURL:aURL resultBlock:^(ALAsset *asset){
       dispatch_async(dispatch_get_main_queue(), ^{
           cell.imageView.image =  [UIImage imageWithCGImage:[asset thumbnail]];
       });
        [[NSNotificationCenter defaultCenter] postNotificationName:@"newAssetImageRetrieved" object:nil];
        //in this notificaton I'm reloading the data; its putting the tableview in an infinite loop - but the images display...

    }
    failureBlock:^(NSError *error){
        // error handling
        NSLog(@"Can't get to assets: FAILED!");
    }];


//cell.imageView.image = [UIImage imageWithCGImage:[asset thumbnail]];

cell.textLabel.text = [NSString stringWithFormat:@"image%d: ready to upload.",indexPath.row];
cell.detailTextLabel.text = [NSString stringWithFormat:@"1.3MB to folder %@", [destinations objectAtIndex:indexPath.row]];
//[[cell imageView] setImage:[UIImage imageWithCGImage:[asset thumbnail]]];


return cell;
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044
rcpilotp51
  • 524
  • 1
  • 3
  • 22

2 Answers2

1

You have to explore all the code base related to the save and retrieve functionality.

However, here are some good tips.

  1. Save the ALAsset Url instead of saving the entire ALAsset as a string.
  2. Retrieve the ALAsset Url from the database and convert it to NSUrlString.
  3. Use ALAsset Library to load the image or thumbnail back.

Hope this will help you.

Shamsudheen TK
  • 30,739
  • 9
  • 69
  • 102
  • thanks for the reply. I was able to successfully save the url using `asset.defaultrepresentation.url` then I'm using `[library assetForURL:aURL resultBlock:^(ALAsset *asset){}` to find the asset (again), but my tableview's cell isn't populating with the thumbnail using `cell.imageView.image = [UIImage imageWithCGImage:[asset thumbnail]];` in the block. I even tried using `ALAssetRepresentation` and something like `[UIImage imageWithCGImage:[assetRep fullResolutionImage]];` and still nothing. Any further suggestions? – rcpilotp51 Apr 03 '14 at 08:49
  • check my this answer http://stackoverflow.com/questions/15335711/iphone-image-loading-in-photos-application/15335963#15335963 – Shamsudheen TK Apr 03 '14 at 08:54
  • also http://stackoverflow.com/questions/13508535/alasset-photo-library-image-performance-improvements-when-its-loading-slow/13508841#13508841 – Shamsudheen TK Apr 03 '14 at 08:55
  • http://stackoverflow.com/questions/13309125/ios-showing-image-from-alasset-in-uitableview – Shamsudheen TK Apr 03 '14 at 08:57
  • I revised my question with my library block – rcpilotp51 Apr 03 '14 at 08:57
  • When i try to adapt your block i get an Exc_Bad_access(code=1). revised the answer with the new block – rcpilotp51 Apr 03 '14 at 09:07
  • I just sent you the implementation file for this question. Thanks – rcpilotp51 Apr 03 '14 at 09:13
  • any further suggestions? – rcpilotp51 Apr 04 '14 at 13:47
  • I got it to the point where i don't get an error but the image thumbnail is only showing when i select the cell – rcpilotp51 Apr 05 '14 at 11:11
  • that's because of, ur doing the UI stuffs in background thread. move the "imageview.image = urimage;" code to the main thread. use the dispatch_async(dispatch_get_main_queue() – Shamsudheen TK Apr 05 '14 at 11:27
  • I am doing that, based off the examples you showed me. The only thing I can think of is I have the assets block in the `cellforrowatindexpath` I'm thinking I should parse the assets in `viewdidload` then `reloaddata`....it doesn't seem to be showing up (see question) – rcpilotp51 Apr 07 '14 at 11:08
  • I've updated my code above "BLOCK" please check it out...this is driving ME NUTS!!! – rcpilotp51 Apr 07 '14 at 20:15
1

There are a couple of issues with your code sample:

  1. The image retrieval is happening asynchronously, so when you try to update the image, you want to make sure the cell is still visible (and not reused for another NSIndexPath).

    In this case, the retrieval from the ALAssetsLibrary will probably be so fast that this isn't critical, but it's a good pattern to familiarize yourself with, because if you're ever retrieving images over the Internet, this issue becomes increasingly important.

  2. Because cells are being reused, if you don't find the image immediately and have to update it asynchronously, make sure you reset the UIImageView before initiating the asynchronous process. Otherwise, you'll see a "flickering" of replacing old images with new ones.

  3. You are using UITableViewCell for your cell. The problem with that is that it will layout the cell based upon the size of the image present by the time cellForRowAtIndexPath finishes.

    There are two easy solutions to this. First, you could initialize the cell's imageView to be a placeholder image of the correct size. (I usually have an image called placeholder.png that is all white or all transparent that I add to my project, which is what I used below.) This will ensure that cell will be laid out properly, so that when you asynchronously set the image later, the cell will be laid out properly already.

    Second, you could alternatively use a custom cell whose layout is fixed in advance, bypassing this annoyance with the standard UITableViewCell, whose layout is contingent upon the initial image used.

  4. I'd suggest using a NSCache to hold the thumbnails images. That will save you from having to constantly re-retrieve the thumbnail images as you get them from your ALAssetsLibrary as you scroll back and forth. Unfortunately, iOS 7 broke some of the wonderful NSCache memory-pressure logic, so I'd suggest a cache that will respond to memory pressure and purge itself if necessary.

Anyway, putting that all together, you get something like:

@interface ViewController ()

@property (nonatomic, strong) NSMutableArray *assetGroups;
@property (nonatomic, strong) ALAssetsLibrary *library;
@property (nonatomic, strong) ThumbnailCache *imageCache;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.imageCache = [[ThumbnailCache alloc] init];

    self.assetGroups = [NSMutableArray array];

    self.library = [[ALAssetsLibrary alloc] init];
    [self.library enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
        if (!group) {
            [self.tableView reloadData];
            return;
        }

        CustomAssetGroup *assetGroup = [[CustomAssetGroup alloc] init];
        assetGroup.name = [group valueForProperty:ALAssetsGroupPropertyName];
        assetGroup.assetURLs = [NSMutableArray array];

        [group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
            if (result) {
                [assetGroup.assetURLs addObject:[result valueForProperty:ALAssetPropertyAssetURL]];
            }
        }];

        [self.assetGroups addObject:assetGroup];
    } failureBlock:^(NSError *error) {
        NSLog(@"%s: enumerateGroupsWithTypes error: %@", __PRETTY_FUNCTION__, error);
    }];
}

#pragma mark - UITableViewDataSource

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return self.assetGroups.count;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    CustomAssetGroup *group = self.assetGroups[section];
    return [group.assetURLs count];
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    CustomAssetGroup *group = self.assetGroups[section];
    return group.name;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *cellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];

    // note, these following three lines are unnecessary if you use cell prototype in Interface Builder

    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
    }

    CustomAssetGroup *group = self.assetGroups[indexPath.section];
    NSURL *url = group.assetURLs[indexPath.row];
    NSString *key = [url absoluteString];
    UIImage *image = [self.imageCache objectForKey:key];

    if (image) {
        cell.imageView.image = image;
    } else {
        UIImage *placeholderImage = [UIImage imageNamed:@"placeholder.png"];
        cell.imageView.image = placeholderImage;  // initialize this to a placeholder image of the right size

        [self.library assetForURL:url resultBlock:^(ALAsset *asset) {
            UIImage *image = [UIImage imageWithCGImage:asset.thumbnail];   // note, use thumbnail, not fullResolutionImage or anything like that

            [self.imageCache setObject:image forKey:key];

            // see if the cell is still visible, and if so, update it
            // note, do _not_ use `cell` when updating the cell image, but rather `updateCell` as shown below

            UITableViewCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath]; // not to be confused with similarly named table view controller method ... this one checks to see if cell is still visible
            if (updateCell) {
                [UIView transitionWithView:updateCell.imageView duration:0.1 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
                    updateCell.imageView.image = image;
                    updateCell.textLabel.text = asset.defaultRepresentation.filename;
                } completion:nil];
            }
        } failureBlock:^(NSError *error) {
            NSLog(@"%s: assetForURL error: %@", __PRETTY_FUNCTION__, error);
        }];
    }

    return cell;
}
@end

The above uses the following classes:

/** Thumbnail cache
 *
 * This cache optimizes retrieval of old thumbnails. This purges itself 
 * upon memory pressure and sets a default countLimit.
 */
@interface ThumbnailCache : NSCache

// nothing needed here

@end

@implementation ThumbnailCache

/** Initialize cell
 *
 * Add observer for UIApplicationDidReceiveMemoryWarningNotification, so it purges itself under memory pressure
 */
- (instancetype)init
{
    self = [super init];
    if (self) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
        self.countLimit = 50;
    };
    return self;
}

/** Dealloc
 *
 * Remove observer before removing cache
 */
- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}

@end

and

/** Custom AssetGroup object
 *
 * This is my model object for keeping track of the name of the group and list of asset URLs.
 */
@interface CustomAssetGroup : NSObject

@property (nonatomic, copy)   NSString       *name;
@property (nonatomic, strong) NSMutableArray *assetURLs;

@end

@implementation CustomAssetGroup

// nothing needed here

@end
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Thanks for you're response (again). Im going to stare at it a bit longer. I see what you're doing with the `CustomAssetsGroup` and the `Thumbnailcache`. right now I'm trying to save everything the the app delegate class `NSMutableArray`. should i make my own? also I'm getting a bunch of Asset Urls from the sql and storing them in an array. can you explain to me the difference in my results block and using the `NSCache` class. And why using simply the `dispatch_async` isn't working. Thanks for the code sample. I'm going to make a new project and get it to work. I'm just curious. – rcpilotp51 Apr 09 '14 at 17:29
  • 1
    @rcpilotp51 The purpose of the cache is that you rarely, if ever, want to hold large objects, like `UIImage` objects in arrays. You want to limit your arrays to very small things (like the URLs). The cache is simply for more memory intensive stuff that you can easily re-retrieve if you have to. – Rob Apr 09 '14 at 18:29
  • 1
    The use of `CustomAssetsGroup` was simply because my table view has multiple sections (corresponding to a "group" in my `ALAssetsLibrary`), and I wanted to encapsulate the name of the group and an array of asset URLs in a single model object. If you don't have sections in your tableview associated with groups in your `ALAssetsLibrary`, then you don't need to do anything like that. – Rob Apr 09 '14 at 18:30
  • 1
    But the use of the cache and the model object may be best practice, but they are all irrelevant to your original question, about how to update the cell image based upon a method that returns asynchronously. The key to that is covered in points 1-3 in my original answer. – Rob Apr 09 '14 at 18:32
  • thanks. I'm going to see if i can get your thumbnail cache class working in another project. – rcpilotp51 Apr 10 '14 at 00:43
  • I added a placeholder.png to the project and set that to the cell's image and kept my dispatch_async and its working now... Sorry, for the delay. With the holiday, I'm just now getting back to this. Thanks Buddy! PS do you think I would ever need the cache if I'm just using assets on the device? I could see how that might be handy if I'm using a web service. Keith – rcpilotp51 Apr 23 '14 at 12:19
  • @rcpilotp51 The cache is still useful with assets, too. By using cache, your app will be just that little bit snappier (no placeholder image, but just show cached image immediately). The very fact that `assetForURL` runs asynchronously is evidence that Apple knows that it runs too slowly for the main queue. – Rob Apr 23 '14 at 12:59