1

I am fetching images synchronously from an array which stores URLs of images but it work very slowly. Now i want to load them asynchronously for fast working.

Heres the code and provide answer with coding.

#import "DetailViewController.h"
#import "FinalViewController.h"

@interface DetailViewController ()

@end

@implementation DetailViewController
@synthesize jsonData;

- (void)viewDidLoad {
    [super viewDidLoad];
    self.title =  @"Select a Photo";

    // Do any additional setup after loading the view.
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

    NSURL *url = [NSURL URLWithString:@"http://json.code.com/albums/1/photos"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [[NSURLConnection alloc] initWithRequest:request delegate:self];


}

-(void)connection:(NSURLConnection *)connection didReceiveResponse:(nonnull NSURLResponse *)response
{
    data1 = [[NSMutableData alloc] init];
}


-(void)connection:(NSURLConnection *)connection didReceiveData:(nonnull NSData *)theData
{
    [data1 appendData:theData];
}

-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;

    jsonArray1 = [NSJSONSerialization JSONObjectWithData:data1 options:nil error:nil];
    [mainTableView reloadData];

}


-(void)connection:(NSURLConnection *)connection didFailWithError:(nonnull NSError *)error
{
    UIAlertView *errorView = [[UIAlertView alloc]initWithTitle:@"Error" message:@"Please make sure you are connected to either 3G or Wi-Fi." delegate:nil cancelButtonTitle:@"Dismiss" otherButtonTitles:nil, nil];
    [errorView show];
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}



- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (int)numberOfSectionInTableView:(UITableView *)tableView
{
    return 1;
}

- (int) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return  [jsonArray1 count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"Cell"];
    }

    cell.textLabel.text = [[jsonArray1 objectAtIndex:indexPath.row] objectForKey:@"title"];
    cell.detailTextLabel.text = [NSString stringWithFormat:@"URL : %@", [[jsonArray1 objectAtIndex:indexPath.row] objectForKey:@"url"]];

    NSURL *URL = [[NSURL alloc] initWithString:[[jsonArray1 objectAtIndex:indexPath.row] valueForKey:@"thumbnailUrl"]];

    NSData *URLData = [[NSData alloc] initWithContentsOfURL:URL];
    [[cell imageView]setImage:[UIImage imageWithData:URLData]];


    return cell;
}


-(void)tableView:(UITableView *)tableview didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    FinalViewController *fvc = [[FinalViewController alloc] initWithNibName:@"FinalViewController" bundle:nil];
    fvc.jsonData2 = [jsonArray1 objectAtIndex:indexPath.row];
    [self.navigationController pushViewController:fvc animated:YES];

}


@end
Navjot Singh
  • 65
  • 12

3 Answers3

1

We can use dispatch_async to run the operation asynchronously.

Try this:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

    myCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
    if (cell == nil) {
        cell = [[myCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
    }

    cell.poster.image = nil; // or cell.poster.image = [UIImage imageNamed:@"placeholder.png"];

    dispatch_async(kBgQueue, ^{
        NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]]];
        if (imgData) {
            UIImage *image = [UIImage imageWithData:imgData];
            if (image) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    myCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];
                    if (updateCell)
                        updateCell.poster.image = image;
                });
            }
        }
    });
    return cell;
}
Janmenjaya
  • 4,149
  • 1
  • 23
  • 43
  • Would you be able to edit this to explain _why_ it might help the reader? What change did you make in this code, or what feature would you draw readers' attention to? – halfer Dec 14 '21 at 23:18
  • 1
    @halfer My intention was to make the reader aware of "dispatch_async" by using which we can achieve asynchronous operation. I have edited the answer with attention point. Thanks – Janmenjaya Dec 21 '21 at 11:50
0

You can do like this:

cell.tag = indexPath.row;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^(void) {

    NSData *imageData = [NSData dataWithContentsOfURL: URL];

    UIImage* image = [[UIImage alloc] initWithData:imageData];
    if (image) {
         dispatch_async(dispatch_get_main_queue(), ^{
             if (cell.tag == indexPath.row) {
                 cell.imageView.image = image;
                 [cell setNeedsLayout];
             }
         });
     }
});

Ref: Asynchronous downloading of images for UITableView with GCD

Community
  • 1
  • 1
tuledev
  • 10,177
  • 4
  • 29
  • 49
  • Can you please help me in putting activity indicator view on the view till photos are loading. need coded help. thanks – Navjot Singh Nov 10 '15 at 05:01
  • I'm thinking about the "the first row is not visible, all the rest are visible now" (maybe not same as origin). Haha. – tuledev Nov 10 '15 at 05:01
  • @NavjotSingh activity indicator, maybe you should post another question. Easy for following. – tuledev Nov 10 '15 at 05:02
  • i can't put more then one question in one day . – Navjot Singh Nov 10 '15 at 05:03
  • 1
    @NavjotSingh well, I don't know that. In my opinion, you shouldn't use indicator. You should use a default image(like placeholder) when loading. – tuledev Nov 10 '15 at 05:30
0

Just by simply setting the following works fine for me .

cell.imageView.image =[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@",[imageArray objectAtIndex:i]]]]];

You can use the activity indicator as you have asked . Just drag and drop UIActivityIndicatorView onto the UIImageView of the UITableViewCell and set the needed constraints . Once the image gets loaded you can set it as hidden .

To do it programmatically , you can add a subview to the Image view in the UITableViewCell. Once the Image gets loaded you can remove the sub view .

UIActivityIndicatorView* actInd = [[UIActivityIndicatorView alloc]init];
[cell.imageView addSubview:actInd];
user5553647
  • 199
  • 2
  • 13