简体   繁体   中英

ios: load new data into UICollectionView

I want to make a application which will display images into UICollectionView.

  • Images will be downloaded from server and then shows into collectionView.
  • I am using custom collectionView layout into xib file.
  • At a time, 20 images is receiving from server.

Problem: I can't show newly downloaded images into collectionView.

Here is my code:

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

    BOOL reloaded = NO;

    static NSString *cellIdentifier = @"cvCell";

    CVCell *cell = (CVCell *)[collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];

    NSMutableArray *data = [self.dataArray objectAtIndex:indexPath.section];

    NSString *cellData = [data objectAtIndex:indexPath.row];

    dispatch_queue_t queue = dispatch_queue_create("com.justTest.anotherSingleApplication", NULL);
    dispatch_async(queue, ^{
        //code to be executed in the background
        NSString *imageName1 = [[NSString alloc]initWithFormat:@"http://www.abc.com/images/thumb/%@", cellData];
        NSString *url_Img1 = imageName1;
        UIImage *aImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:url_Img1]]];

        dispatch_async(dispatch_get_main_queue(), ^{
            //code to be executed on the main thread when background task is finished
            [cell.cellImage setImage:aImage];

        });
    });

    if (indexPath.row == self.imageArray.count - 1 && !reloaded) {

        getOnScrollImages *getImage = [[getOnScrollImages alloc] init]; // class to get image name from server
        NSMutableArray *astring = (NSMutableArray *)[getImage getImageNameFromServer:@"list" board:@"111" pin:@"122345"]; // method to get image name from server

        [self setNewTestArray:astring]; //adding newly downloaded image name into array

        reloaded = YES;

        dispatch_async(dispatch_get_main_queue(), ^{
            [self.collectionView reloadData];
        });
    }

    return cell;
}

Any suggestion please?

NOTE: I am just starting developing iOS application, this may be a very silly question.

Use asynchronously fetch to get data from server and display it in collectionView

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    YourDataModel *model = self.dataArray[indexPath.row];    
    YourCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];

    if ([self checkWhetherImageAlreadyExist]) {

        [cell.imageView setImage:model.image];

    } else {
        //show placeholder to avoid nothing in your UI, or your user gets confused
        [cell.imageView setImage:placeholderImage];
        [self startDownloadImageForIndexPath:indexPath];
    }
}

- (void)startDownloadImageForIndexPath:(NSIndexPath *)indexPath
{
    //YourImageDownloader is a class to fetch data from server
    //imageDownloadsInProgress is a NSMutableDictionary to record the download process, which can avoid repeat download
    YourImageDownloader *downloader = [self.imageDownloadsInProgress objectForKey:indexPath];

    if (downloader == nil) {
        YourDataModel *model = self.dataArray[indexPath.row];

        //configure downloader
        downloader = [[YourImageDownloader alloc] init];
        [downloader setURL:model.url];
        [downloader setCompletionHandler:^{
            //download the image to local, or you can pass the image to the block
            model.image = [UIImage imageWithContentsOfFile:model.localPath];
            YourCell *cell = [self.mCollectionView cellForItemAtIndexPath:indexPath];
            [cell.imageView setImage:model.image];

            //remove downloader from dictionary
            [self.imageDownloadsInProgress removeObjectForKey:indexPath];
        }];

        //add downloader to dictionary
        [self.imageDownloadsInProgress setObject:downloader forKey:indexPath];

        //start download
        [downloader startDownload];
    }
}

Use a class to download the image. If you have many images in one collection view, you may consider to save these images to local in case of memory warning. if now many, just leave the image in memory and display it in your collection view.

the code followed is save the image to local and read image data from local when displaying.

in .h:

#import <Foundation/Foundation.h>

@interface PortraitDownloader : NSObject

@property (nonatomic, copy) NSString *portraitName;
@property (nonatomic, copy) void (^completionHandler)(void);

- (void)startDownload;
- (void)cancelDownload;

@end

in .m

#import "PortraitDownloader.h"
#import <CFNetwork/CFNetwork.h>
#import "NSString+ImagePath.h" // it's a category to get the image local path

@interface PortraitDownloader ()

@property (nonatomic, strong) NSMutableData *activeDownload;
@property (nonatomic, strong) NSURLConnection *portraitConnection;

@end

@implementation PortraitDownloader

- (void)startDownload
{
    self.activeDownload = [NSMutableData data];

    NSString *urlstr = [NSString serverPortraitPathWithPortrait:self.portraitName];
    NSURL *url = [NSURL URLWithString:urlstr];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    self.portraitConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}

- (void)cancelDownload
{
    [self.portraitConnection cancel];
    self.portraitConnection = nil;
    self.activeDownload = nil;
}

#pragma mark - NSURLConnectionDelegate

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    [self.activeDownload appendData:data];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    // Clear the activeDownload property to allow later attempts
    self.activeDownload = nil;

    // Release the connection now that it's finished
    self.portraitConnection = nil;
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    // save to local path
    NSString *localSavePath = [NSString localPortraitPathWithPortrait:self.portraitName];
    [self.activeDownload writeToFile:localSavePath atomically:YES];

    self.activeDownload = nil;

    // Release the connection now that it's finished
    self.portraitConnection = nil;

    // call our delegate and tell it that our icon is ready for display
    if (self.completionHandler) {
        self.completionHandler();
    }
}

@end

if you want to leave your image in-memory, just modify the completion block as:

in .h

typedef void (^Completion_handle) (UIImage *image);

@interface PortraitDownloader : NSObject

@property (nonatomic, copy) Completion_handle myCompletionBlock;

and in .m

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    // get image from data
    UIImage *image = [UIImage imageWithData:self.activeDownload];

    self.activeDownload = nil;

    // Release the connection now that it's finished
    self.portraitConnection = nil;

    // call our delegate and tell it that our icon is ready for display
    if (self.myCompletionBlock) {
        self.myCompletionBlock(image);
    }
}

and also modify methods startDownloadImageForIndexPath, save the image to your model to retain it

This method expects to have answers immediately:

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

when your code doesn't respond fast enough to it, the app will usually display nothing, or sometimes just crash (depending on what you've setup)

A common design pattern is to store the info that will be supplied to the collectionView in a class variable (it doesn't have to be a property, but it often times is). You always store SOMETHING in that variable, even if it is old or stale data.

Then you have the methods defined in the UICollectionViewDataSource protocol pull what they need directly from the class variables, with no delay.

Other methods can fetch and retrieve and sling updated data around, and once they finish you call reloadData: on the collectionView to update the interface.

assuming the asynchronous calls you are using are successfully retrieving data eventually, they are probably too slow for what the UICollectionViewDataSource protocol methods are expecting.

A suggestion for how to get started would be to move the code fetching your data to separate methods, and then stage the data in a class variable or two which the collectionView can reliably draw from.

You can try it with static data loaded into the bundle at first if you need, and then move into asynchronous pulls from the web too.

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
 UICollectionViewCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:@"cellIdentifier" forIndexPath:indexPath];
 UIImageView *imgView=[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"profile_pic.png"]];
 NSMutableDictionary *contactData=[NSMutableDictionary new];
 contactData = [self.collectionData objectAtIndex:indexPath.row];
 imgView.image=[contactData objectForKey:@"image"];
 [cell addSubview:imgView];
 return cell;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM