Question

When I load the images to show to the UICollectionView I load all the images from the array like this

- (void)viewDidLoad
{
    allImagesArray = [[NSMutableArray alloc] init];
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *location=@"Others";
    NSString *fPath = [documentsDirectory stringByAppendingPathComponent:location];
    NSArray *directoryContent = [[NSFileManager defaultManager] directoryContentsAtPath: fPath];
    collectionOthers.delegate =self;
    collectionOthers.dataSource=self;
    for(NSString *str in directoryContent)
    {
        NSString *finalFilePath = [fPath stringByAppendingPathComponent:str];
        NSData *data = [NSData dataWithContentsOfFile:finalFilePath];
        if(data)
        {
            UIImage *image = [UIImage imageWithData:data];
            [allImagesArray addObject:image];
            NSLog(@"array:%@",[allImagesArray description]);
            image = nil;

        }
        finalFilePath=nil;
        data=nil;
    }

    paths= nil;
    documentsDirectory= nil;
    location= nil;
    fPath= nil;
    directoryContent = nil;
}

This is the biggest issue in my app since it uses so many memory. It is because number and size of the images, this could just take up memory. I would only want to load images when they are needed, and discard them when they are no longer needed.However I do not know where and how to change my code so that it will be that way. I am doing this for three month or so and I really need help.
Update
This is my code for the specific part

-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *reuseID = @"ReuseID";
    OthersCell *mycell = (OthersCell *) [collectionView dequeueReusableCellWithReuseIdentifier:reuseID forIndexPath:indexPath];
    UIImageView *imageInCell = (UIImageView*)[mycell viewWithTag:1];
    imageInCell.image = [allImagesArray objectAtIndex:indexPath.row];
    NSLog(@"a");
    return mycell;
}
Was it helpful?

Solution

Clearly, you should load the images just-in-time. One should never hold an array of images (because they take up a lot of memory), but rather just hold an array of filenames. So I'm suggesting you retire allImagesArray and instead define a NSMutableArray called filenames. You could then create the UIImage objects on the fly:

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *cellIdentifier = @"Cell";
    OthersCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
    UIImageView *imageInCell = (UIImageView*)[cell viewWithTag:1];

    imageInCell.image = [UIImage imageWithContentsOfFile:filenames[indexPath.item]];

    return cell;
}

This, assumes, of course, that you populated this NSMutableArray of filenames in viewDidLoad:

- (void)viewDidLoad
{
    filenames = [[NSMutableArray alloc] init];
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *location=@"Others";
    NSString *fPath = [documentsDirectory stringByAppendingPathComponent:location];
    NSArray *directoryContent = [[NSFileManager defaultManager] directoryContentsAtPath: fPath];
    collectionOthers.delegate =self;
    collectionOthers.dataSource=self;
    for(NSString *str in directoryContent)
    {
        NSString *finalFilePath = [fPath stringByAppendingPathComponent:str];
        [filenames addObject:fileFilePath];
    }
}

This has a problem, though, because imageWithContentsOfFile (as well as loading it into a NSData first and then doing imageWithData) is a bit slow if the images aren't tiny. On slower devices, this can result in a slight stuttering of a quick scroll of a collection view. So, a better approach would be to (a) load the images asynchronously; (b) use a NSCache to optimize performance for when you scroll backwards.

So, first, define a cache:

@property (nonatomic, strong) NSCache *imageCache;

And, instantiate this in viewDidLoad:

self.imageCache = [[NSCache alloc] init];
self.imageCache.name = @"com.company.app.imageCache";

And then, cellForItemAtIndexPath can (a) set the image from the cache; and (b) if not found, retrieve the image asynchronously updating cache and cell appropriately, e.g.:

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *cellIdentifier = @"Cell";
    OthersCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
    UIImageView *imageInCell = (UIImageView*)[cell viewWithTag:1];

    NSString *cacheKey = filenames[indexPath.item];
    imageInCell.image = [self.imageCache objectForKey:cacheKey];

    if (imageInCell.image == nil) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            UIImage *image = [UIImage imageWithContentsOfFile:filenames[indexPath.item]];
            if (image) {
                [self.imageCache setObject:image forKey:cacheKey];
                dispatch_async(dispatch_get_main_queue(), ^{
                    OthersCell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];
                    UIImageView *imageInCell = (UIImageView*)[updateCell viewWithTag:1];
                    imageInCell.image = image;
                });
            }
        });
    }

    return cell;
}

And, obviously, make sure you purge the cache if you receive memory warnings (in iOS 7, the cache doesn't always automatically purge itself under pressure like it used to do):

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];

    [self.imageCache removeAllObjects];
}

OTHER TIPS

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
    cellForItemAtIndexPath:(NSIndexPath *)indexPath

This is the method in which you should be loading the images.

In viewDidLoad, I'd build the array of NSString file paths to each image, then I'd use the collectionView:cellForItemAtIndexPath: method to load the image from the specific file path for this particular cell.

In viewDidLoad You could just load a list of available images. So remove the for loop: for(NSString *str in directoryContent) { ... } loop there (EDIT: or make it a simple for loop, just to populate an array with filenames for the files having data).

When you update a specific collectionviewcell in collectionView:cellForItemAtIndexPath:, just load the image (only 1). The cell will now hold the image data instead of your view controller. So when the cell is released, so is the image data.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top