Monday, May 21, 2012

Lazy Loading of Images in Table (iOS : 5.0)


Hello all,

I know many of us have successfully done this.
I have tried to show the working via images. Hope it helps all the beginners like me to learn more.

What you can get here:

1) How to start with Story-Boards
2) Url Encoding
3) Lazy loading of images


Here is a tutorial for all,

Step -1 : Open New Project in Xcode :






Step - 2 : Open MainStoryBoard.storyboard

Inside the View add a tableview and connect its DataSource and Delegate with the controller class.
You can also change the background color of the view to have a look like this : 





Step - 3 : Now Open your LazyLoadingViewController.h file and write :


@interface LazyLoadingViewController : UIViewController<UITableViewDelegate,UITableViewDataSource>
{
    IBOutlet UITableView            *tableView;
    NSArray                         *animals;
    NSArray                         *animalUrl;
    NSMutableDictionary             *imageDict;
}
@property(nonatomic,retain)UITableView              *tableView;
@property(nonatomic,retain)NSArray                  *animals;
@property(nonatomic,retain)NSArray                  *animalUrl;
@property(nonatomic,retain)NSMutableDictionary      *imageDict;

-(NSString *) URLEncodeString:(NSString *) str;
@end


Step - 4 : Now Open your LazyLoadingViewController.m file and write this : 

a) Synthesize all the properties that you created in .h class
b) Initialize your arrays and dictionary in ViewDidLoad.
       I have done like this : 

    imageDict = [[NSMutableDictionary alloc] initWithCapacity:4];
    animals = [NSArray arrayWithObjects:@"CAT",@"DOG",@"FOX",@"LION", nil];
    animalUrl = [NSArray arrayWithObjects:
         @"http://www.zastavki.com/pictures/1600x1200/2008/Animals_Cats_Small_cat_005241_.jpg",
         @"http://spadogbotanicals.com/wp-content/uploads/puppy1-300x225.jpg",
         @"http://www.nj.gov/dep/fgw/images/wildlife/fox_gl.jpg",
         @"http://www.mikesel.info/wp-content/uploads/2011/07/lion.jpg",nil];


c) Now add all table view methods : 


In our case the number of sections is only one but the rows depend on the total contents in the array we will use to display the contents in the table view


- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 
{
    return 1;
}

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



d) For cell creation we want to display a default image in the image view till we get the image from server.In our case we have used this :


e) Cell creation is the most important part of the table view  that will display the contents from the web when they are available. This is done via dispatching a background thread that fetches the images in background.



- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *Identifier=@"Identifier";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:Identifier];
if(cell == nil)
{
cell = [self reuseTableViewCellWithIdentifier:Identifier withIndexPath:indexPath];
 cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.selectionStyle = UITableViewCellSelectionStyleNone
}
//configure cell
UIImageView *imgView= (UIImageView *)[cell.contentView viewWithTag:IMG_TAG];
if([imageDict  valueForKey:[NSString stringWithFormat:@"%i,%i", indexPath.row, indexPath.section]]==nil)
{
[NSThread detachNewThreadSelector:@selector(displayingSmallImage:) 
            toTarget:self withObject:indexPath];
}
else
{
imgView.image = [imageDict valueForKey:[NSString stringWithFormat:@"%i,%i",
                                          indexPath.row, indexPath.section]];
}
UILabel *lblName = (UILabel *)[cell.contentView viewWithTag:TITLE_TAG];
[lblName setText:[animals objectAtIndex:indexPath.row]];
return cell;
}


-(UITableViewCell *)reuseTableViewCellWithIdentifier:(NSString *)identifier withIndexPath:(NSIndexPath *)indexPath 
{
CGRect cellRectangle;
cellRectangle = CGRectMake(0.0, 0.0, 340, 180);
UITableViewCell *cell = [[UITableViewCell alloc]
                         initWithFrame:cellRectangle reuseIdentifier:identifier];

//here we create the image view and set its properties

UIImageView *img11;
cellRectangle = CGRectMake(15,18, 75, 70);
img11 = [[UIImageView alloc] initWithFrame:cellRectangle];
img11.tag = IMG_TAG;
img11.image = [UIImage imageNamed:@"base-image.png"] ;//acts as default image
img11.contentMode = UIViewContentModeScaleToFill;
[cell.contentView addSubview:img11];
//here we create the label and set its properties

UILabel *labelName;
cellRectangle = CGRectMake(150, 40, 200, 20);
labelName = [[UILabel alloc] initWithFrame:cellRectangle];
labelName.font =[UIFont fontWithName: @"Verdana"size:15.0f];
labelName.textColor = [UIColor blackColor];
labelName.autoresizingMask = UIViewAutoresizingFlexibleWidth;
labelName.tag = TITLE_TAG;
 [cell.contentView addSubview:labelName];

return cell;
}

Note : Remember to add the labels and the image view to the cell's content view.


f) Encode URL : This is done to create verbatim string for url to override the behavior of the escape sequences.

-(NSString *) URLEncodeString:(NSString *) str
{
    
    NSMutableString *tempStr = [NSMutableString stringWithString:str];
    [tempStr replaceOccurrencesOfString:@" " withString:@"+" options:NSCaseInsensitiveSearch range:NSMakeRange(0, [tempStr length])];
    
    
    return [[NSString stringWithFormat:@"%@",tempStr] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
}

g) Cell creation is the most important part of the table view  that will display the contents from the web when they are available.If Image is available from the server we will display it else we will display the default image. You can see the test condition for this in BOLD.

After we get the image we save it in the dictionary and display the image on the image view in main thread i.e when the working of background thread is complete.


- (void) displayingSmallImage:(NSIndexPath *)indexPath
{
NSString *imageUrl = [animalUrl objectAtIndex:indexPath.row];
 imageUrl = [self URLEncodeString:imageUrl];
   
NSURL *url = [NSURL URLWithString:imageUrl];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
if(image == nil)
{
image = [UIImage imageWithContentsOfFile:[[[NSBundle mainBundle
               resourcePath] stringByAppendingPathComponent:@"base-image.png"]];
}
[self.imageDict setObject:image forKey:[NSString
                 stringWithFormat:@"%i,%i",indexPath.row,indexPath.section]];

[self performSelectorOnMainThread:@selector(imageReceived:) 
                        withObject:indexPath 
                     waitUntilDone:NO];
}



h) Here we display the image received and reload the particular row of the table for which we got the image.

- (void) imageReceived:(NSIndexPath *)indexPath

{
UIImage *image = (UIImage *)[imageDict objectForKey:
                             [NSString stringWithFormat:@"%i,%i",
                             indexPath.row,indexPath.section]];
UIImageView *imgs = (UIImageView *)[[tableView
                     cellForRowAtIndexPath:indexPath] viewWithTag:IMG_TAG];
[imgs setImage:image];
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                  withRowAnimation:UITableViewRowAnimationNone];
}




i) Don't forget to add this tag in your .m file : This is done so that the labels and image views when created we assign a tag to them so that while giving them their values the same can be accessed via tags.




#define IMG_TAG   1
#define TITLE_TAG 2

This is to be done after the properties are synthesized.

j) You will see output like this : 

  i) When the Application is launched.

ii) When only 2 icons were fetched and rest are in transit


iii) When all icons were fetched from the web server







Enjoy table view and iOS....... :)









14 comments:

  1. hi swati nice tutorial, thanks a lot and i have a doubt like as you are using NSArray of objects and links directly in the code, if i have an xml file in url and from that i wanna show the data and images then how come i can rearrange the code. hope you got wat am asking Thank u swati

    ReplyDelete
  2. For this
    First you need to get the xml file using the URL (NSURL)
    Then u need to parse this XML and save data and URL for images in an array
    Now show the images in table view

    ReplyDelete
    Replies
    1. ya swati i did so and i face some troubles in it can you see this link you can get what my prob is http://stackoverflow.com/questions/13696406/why-am-viewing-only-uiimage-of-last-element-in-xml-file-from-url-in-iphone/13696528#comment18807682_13696528

      Delete
  3. hi as we previously discussed, i had used the code,
    xmlParser = [[XMLParsing alloc] loadXMLByURL:@"http://images.com/Products.xml"];
    in viewDidLoad() of table controller file and from there
    in - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
    return [animals count];
    }

    i used return [xmlParser.tweets count]; and NSLog(@"Table images %@",xmlParser.tweets); but this didn't give any NSLog output but if i use the same in NSLog(@"Table images %@",xmlParser.tweets);in viewDidLoad() i can get the values of images. i cant understand why its not showing in the tableView numberOfRowsInSection:(NSInteger)section any ideas?

    ReplyDelete
    Replies
    1. in method : numberOfRowsInSection
      have you checked that parser has some memory or not??
      also try to access xmlParser using self.xmlParser

      Delete
  4. hi swati can you see this link and help me out to solve http://stackoverflow.com/questions/13716716/why-images-not-loading-from-xml-file-of-url-and-not-showing-in-table-view

    ReplyDelete
  5. Hi Swati this is me again can you help me out to solve this problem
    http://stackoverflow.com/questions/14600396/why-the-same-images-are-repeated-in-all-the-collection-views-of-iphone-sdk/14602624#14602624

    ReplyDelete
  6. HI, Thanks for the code, It works well with less no of images in Table view, But there is a problem with more no of images. say If i have 20 no of images that i want to load in to my table, when scrolling images are repeating until the actual image loads.

    ReplyDelete
    Replies
    1. Prefer using Show more button at the end. Clicking on Show more produces/displays next 20 images

      Delete
  7. i need lazy image loading in collection view controller...give some ideas.

    ReplyDelete
  8. wherever imagetag and title tag is used in the program ..
    it is giving a warning "expected expression"
    can;t figure out why???

    ReplyDelete
    Replies
    1. sorry for the late reply, Have u # defined IMG_TAG in your code

      Delete
  9. i have used your code but it is frizzed my app i have download more than 200 images from server then it is frizzed

    ReplyDelete
    Replies
    1. When such a big number of images are downloaded try to save images with u, show 20 images and a button indicating "show more". when user clicks on show more next 20 images are fetched and displayed...

      Delete