Hi I'm using GCD to load image in the uitableview . Now, the problem is image animation. I called three images from server it loads into the cell but i got 3 problems
1) Single image is repeated in all three cells
2) first and last cell images blinking and changing
3) when the image changed, it is repeated in all cells.
Even though i gave animation none still it animates i.e. Blinking
Code:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell...
[imageArray retain];
//get a dispatch queue
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//this will start the image loading in bg
dispatch_async(concurrentQueue, ^{
NSData *image = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:[imageArray objectAtIndex:indexPath.row]]];
//this will set the image when loading is finished
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView.image = [UIImage imageWithData:image];
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
});
});
return cell;
}
You should not download images in this place.
If You don't have too many rows, do this in eg. viewDidLoad and save downloaded images to new mutable array.
Something like this:
- (void) viewDidLoad {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
int row = 0;
self.thumbnails = [NSMutableArray array];
for (NSString *imageURLString in imageArray) {
//Your way of download images
NSData *imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:imageURLString]];
UIImage *image = [UIImage imageWithData:imageData];
dispatch_async(dispatch_get_main_queue(), ^{
[self.thumbnails addObject:image];
NSArray *indexes = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:row inSection:1]];
[self.tableViewOutlet reloadRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationNone];
});
row++;
}
});
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (self.thumbnails && [self.thumbnails count] > indexPath.row) {
cell.attachmentImageView.image = [self.thumbnails objectAtIndex:indexPath.row];
} else {
cell.attachmentImageView.image = nil;
}
}
Related
I have a segment and a table view, I have to reload the data in the table view its working but when i switch the segment buttons fast then application crashes.
On segment i have three buttons and on their action I am triggering a request to a url and fetching the json from that url (3 different urls resp. and returns 10, 17 and 8 results in json).
I have a custom table cell which have the images in it and i am trying to lazily load theose images.
In JSON result each object of json have various keys in it like name, id, imageurl etc.
the id is unique throughout the application and I am trying to load the images and save the images in local memory based on that unique id, so that i can show the cashed images and no need to redownload the images on segment change because some enteries on all three segments are same.
Here is my complete code
- (IBAction)segmentedControlIndexChanged {
NSString *offerRequestUrl = nil;
switch (self.mySegment.selectedSegmentIndex) {
case 0:
offerRequestUrl = #"url_one";
self.feedRequestUrl = [NSURL URLWithString:offerRequestUrl];
break;
case 1:
offerRequestUrl = #"url_two";;
self.feedRequestUrl = [NSURL URLWithString:offerRequestUrl];
break;
case 2:
offerRequestUrl = #"url_three";;
self.feedRequestUrl = [NSURL URLWithString:offerRequestUrl];
break;
default:
break;
}
[self parseJSONWithURL:self.feedRequestUrl];
}
- (void) parseJSONWithURL:(NSURL *) jsonURL
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData* data = [NSData dataWithContentsOfURL: jsonURL];
[self performSelectorOnMainThread:#selector(fetchedData:) withObject:data waitUntilDone:YES];
});
}
- (void)fetchedData:(NSData *)responseData {
//parse out the json data
NSError* error;
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:responseData
options:kNilOptions
error:&error];
self.feedsArray = [json objectForKey:#"result"];
[self.myTableView reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
int count = self.feedsArray.count;
// if there's no data yet, return enough rows to fill the screen
if (count == 0)
{
return 1;
}
return count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// customize the appearance of table view cells
//
static NSString *CellIdentifier = #"MyTableCell";
static NSString *PlaceholderCellIdentifier = #"PlaceholderCell";
// add a placeholder cell while waiting on table data
int nodeCount = [self.feedsArray count];
if (nodeCount == 0 && indexPath.row == 0)
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:PlaceholderCellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:PlaceholderCellIdentifier];
cell.detailTextLabel.textAlignment = UITextAlignmentCenter;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
cell.detailTextLabel.text = #"Loading . . .";
[self performSelector:#selector(checkAndDisplayAlert) withObject:self afterDelay:10.0];
return cell;
}
CustomCell *cell = (CustomCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (nodeCount > 0)
{
// Configure the cell...
cell.carImageView.image = [UIImage imageNamed:#"Placeholder"]; //acts as default image
if([self.imageDict valueForKey:[NSString stringWithFormat:#"%#", self.feedsArray[indexPath.row][#"id_str"]]]==nil)
{
[NSThread detachNewThreadSelector:#selector(displayingSmallImage:) toTarget:self withObject:indexPath];
} else {
cell.carImageView.image = [self.imageDict valueForKey:[NSString stringWithFormat:#"%#", self.feedsArray[indexPath.row][#"id_str"]]];
}
cell.imageView.clipsToBounds = YES;
cell.titleLabel.text = self.feedsArray[indexPath.row][#"title_key"];
}
return cell;
}
- (void) displayingSmallImage:(NSIndexPath *)indexPath
{
NSString *imageUrl = self.feedsArray[indexPath.row][#"icon_url"];
NSURL *url = [NSURL URLWithString:imageUrl];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
if(image == nil)
{
image = [UIImage imageWithContentsOfFile:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"Placeholder"]];
}
[self.imageDict setObject:image forKey:[NSString stringWithFormat:#"%#", self.feedsArray[indexPath.row][#"id_str"]]];
[self performSelectorOnMainThread:#selector(imageReceived:) withObject:indexPath waitUntilDone:NO];
}
- (void) imageReceived:(NSIndexPath *)indexPath
{
UIImage *image = (UIImage *)[self.imageDict objectForKey: [NSString stringWithFormat:#"%#", self.feedsArray[indexPath.row][#"id_str"]]];
UIImageView *imgs = (UIImageView *)[[self.myTableView cellForRowAtIndexPath:indexPath] viewWithTag:IMG_TAG];
[imgs setImage:image];
[self.myTableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
- (void) checkAndDisplayAlert
{
if (!self.feedsArray && self.isAlertPresent == NO) {
UIAlertView *nilAlert = [[UIAlertView alloc] initWithTitle:#"Sorry!"
message:#"No result returned from server."
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[nilAlert setTag:kNilAlertTag];
self.isAlertPresent = YES;
[nilAlert show];
}
}
The main things I have to do is
lazily download the images
cache the images in local memory based on unique ID
Check the cashed images dictionary for already downloaded image and display it
If possible save these downloaded images in the local memory (nsuserdefaults etc) so that if user restart the app then also images are there and can be displayed based on their unique ID and no need to download again.
Any help would be appreciated.
Please post the error log, if it is array out of bounds exception I suspect these two methods
- (void)fetchedData:(NSData *)responseData & your tableViewDatasource
To find which line of code causes this crash do this. Go to break points tab on the left side and click + in the bottom.
Then you would get something like this:
Just hit done, run your code again and tell us which line caused the exception.
I understood your problem, why do you want to take the URL from the array in this function
- (void) displayingSmallImage:(NSIndexPath *)indexPath
you could pass the Url to load to that function, from cellForRowAtIndex like this:
NSArray *argsToPass=[NSArray arrayWithObjects:indexPath,self.feedsArray[indexPath.row][#"icon_url"]];
[NSThread detachNewThreadSelector:#selector(displayingSmallImage:) toTarget:self withObject:argsToPass];
and your function is modified as:
- (void) displayingSmallImage:(NSArray *)args
{
if([args count] == 0)
{
NSString *imageUrl = [args objectAtIndex:1];
NSIndexPath *indexPath=[args objectAtIndex:0];
NSURL *url = [NSURL URLWithString:imageUrl];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
if(image == nil)
{
image = [UIImage imageWithContentsOfFile:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"Placeholder"]];
}
[self.imageDict setObject:image forKey:[NSString stringWithFormat:#"%#", self.feedsArray[indexPath.row][#"id_str"]]];
[self performSelectorOnMainThread:#selector(imageReceived:) withObject:indexPath waitUntilDone:NO];
}
}
you can set self.view.userInteractionEnabled=NO when you are sending request to server.
and when u get the data just enable the user interaction .so that multiple requests are avoided.
I have a uitableview and each cells have an image from facebook image profile and 2 uilabels (from singleton, not an internet request)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
static NSString *CellIdentifier = #"MenuCell";
MenuCellViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[MenuCellViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
//Singleton
DataModel *m = [DataModel getModel];
switch ([indexPath section]) {
case 0: //Only Header
break;
case 1: //My cells
{
//If footer - set footer background
if (indexPath.row == [tableView numberOfRowsInSection:indexPath.section] - 1){
[cell setBackgroundView:[[UIImageView alloc]initWithImage:[UIImage imageNamed:#"imgFooter.png"]]];
//Else - set normal cell background
} else {
[cell setBackgroundView:[[UIImageView alloc]initWithImage:[UIImage imageNamed:#"img.png"]]];
}
//Get Facebook image
strForImg = [[NSString alloc] initWithFormat:#"https://graph.facebook.com/%#/picture?type=square",[[[m list] objectAtIndex:indexPath.row] objectForKey:#"id"]];
url =[NSURL URLWithString:strForImg];
img = [UIImage imageWithData: [NSData dataWithContentsOfURL:url]];
[cell.imgProfile setImage:img];
//Set some text (uilabels)
cell.lblNom.text = [[[m list] objectAtIndex:indexPath.row] objectForKey:#"name"];
cell.lblTour.text = [[[m list] objectAtIndex:indexPath.row] objectForKey:#"description"];
break;
}
}
}
This code is very slow and freezes the tableview as it loads from facebook the profile images of each cell dynamically.
I tried doing with a dispatch like this
case 1://My cells
{
if (indexPath.row == [tableView numberOfRowsInSection:indexPath.section] - 1){
[cell setBackgroundView:[[UIImageView alloc]initWithImage:[UIImage imageNamed:#"imgFooter.png"]]];
} else {
[cell setBackgroundView:[[UIImageView alloc]initWithImage:[UIImage imageNamed:#"img.png"]]];
}
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
dispatch_sync(dispatch_get_main_queue(), ^{
strForImg = [[NSString alloc] initWithFormat:#"https://graph.facebook.com/%#/picture?type=square",[[list objectAtIndex:indexPath.row] objectForKey:#"id"]];
url =[NSURL URLWithString:strForImg];
img = [UIImage imageWithData: [NSData dataWithContentsOfURL:url]];
[cell.imgProfile setImage:img];
});
});
cell.lblNom.text = [[[m list] objectAtIndex:indexPath.row] objectForKey:#"name"];
cell.lblTour.text = [[[m list] objectAtIndex:indexPath.row] objectForKey:#"description"];
break;
}
The speed has slighty increased but it is still FREEZING when scrolling.
Is my dispatch not correct ? (I have just discovered it, maybe it is not well used).
Or is there any better method ?
I like the facebook friend picker. It puts a default profile image and refreshes the correct image profile when it is received from internet. How is this magic done ? :)
Fix this block:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
strForImg = [[NSString alloc] initWithFormat:#"https://graph.facebook.com/%#/picture?type=square",[[list objectAtIndex:indexPath.row] objectForKey:#"id"]];
url =[NSURL URLWithString:strForImg];
img = [UIImage imageWithData: [NSData dataWithContentsOfURL:url]];
dispatch_sync(dispatch_get_main_queue(), ^{
[cell.imgProfile setImage:img];
});
});
I am using [NSOperationQueue mainQueue] and NSInvocationOperation in table view cellforRowAtIndexPath method to load images from web in background.But it is not stopping after download image. Here is my code
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell=[mytable dequeueReusableCellWithIdentifier:#"cell"];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"];
}
imageLabel=(UIImageView *)[cell viewWithTag:5];
UIImage *image=[dict objectForKey:[appSmallLogos objectAtIndex:indexPath.row]];
if (image) {
imageLabel.image=image;
} else {
NSURL *on=[appSmallLogos objectAtIndex:indexPath.row];
NSString *on1=[appNames objectAtIndex:indexPath.row];
NSMutableArray *array=[[NSMutableArray alloc] initWithObjects:on,on1,indexPath, nil];
operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(didGetAlbums:) object:arr];
q =[NSOperationQueue mainQueue];
[q addOperation:operation];
imageLabel.image=[dict objectForKey:on1];
}
return cell;
}
//didgetalbums method
- (void )didGetAlbums: (NSMutableArray* )url
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSData *data=[NSData dataWithContentsOfURL:[url objectAtIndex:0]];
UIImage *image1 = [UIImage imageWithData:data];
dispatch_sync(dispatch_get_main_queue(), ^{
[dict setObject:image1 forKey:[url objectAtIndex:1]];
});
});
//reload the requested cell after download image
[self. mytable beginUpdates];
[self. mytable reloadRowsAtIndexPaths:[NSArray arrayWithObjects:[url objectAtIndex:2], nil] withRowAnimation:UITableViewRowAnimationNone];
[self. mytable endUpdates]; // imageLabel.image=[dict objectForKey:[appSmallLogos objectAtIndex:indexPath.row]];
}
I'm trying to load images into a table but I am still getting the flickering. Images are loading, but when scrolling there is a short flicker before the next thumbnail image loads. Yes, know Apple has an example, and various frameworks, but this is super simple code, just that damn flickering before the next image is loaded. Everything else works fine.
Thanks!
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"customCell";
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil){
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"CustomCell" owner:nil options:nil];
for (id currentObject in topLevelObjects) {
if ([currentObject isKindOfClass:[CustomCell class]]) {
cell = (CustomCell *)currentObject;
break;
}
}
}
NSString *myUrl = [[items objectAtIndex:indexPath.row] objectForKey:#"myUrl"];
NSURL *url = [NSURL URLWithString:url];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(queue, ^{
UIImage *image = [UIImage imageWithData: [NSData dataWithContentsOfURL:url]];
dispatch_sync(dispatch_get_main_queue(), ^{
[[cell imageView] setImage:image];
[cell setNeedsLayout];
});
});
return cell;
}
You are using the reusing mechanism of UITableView. That means you can get a cell from dequeueReusableCellWithIdentifier which already has an image set. So at first you need to remove the image with [[cell imageView] setImage:nil]. Then inside the dispatch block you can't be sure that the cell is still on-screen. You need to check if the index path is still the same, otherwise you would set the image to a wrong cell:
dispatch_async(queue, ^{
UIImage *image = [UIImage imageWithData: [NSData dataWithContentsOfURL:url]];
dispatch_sync(dispatch_get_main_queue(), ^{
if ([[tableView indexPathForCell:cell] compare:indexPath] == NSOrderedSame) {
[[cell imageView] setImage:image];
[cell setNeedsLayout];
}
// alternative:
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
if (cell)
cell.imageView.image = image;
});
});
I recommend to use an NSOperation subclass to load the remote images instead of using dataWithContentsOfURL - then you can also cancel image loading when a cell goes offscreen.
Also consider using an image cache for better performance.
I had a similar issue you need to check to see if the image is already loaded. Your current code is just loading the images, but what if the cell already has an image? You do not want to perform this task every time the user scrolls the table.
Check out my questions I asked.
Table View Scrolling Async
Kindly look the below code..i found from web
- (void) loadData {
NSOperationQueue *queue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(loadDataWithOperation) object:nil];
[queue addOperation:operation];
[operation release];
}
- (void) loadDataWithOperation {
NSURL *dataURL = [NSURL URLWithString:#"http://icodeblog.com/samples/nsoperation/data.plist"];
NSArray *tmp_array = [NSArray arrayWithContentsOfURL:dataURL];
for(NSString *str in tmp_array) {
[self.array addObject:str];
}
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
[cell.textLabel setText:[self.array objectAtIndex:indexPath.row]];
return cell;
}
I have found the above code which loads the text from the web without any problem.( i mean gui is not hanging ).like wise i want to load images into the table view.for that i have wriiten the below code.
- (void) loadDataWithOperation {
NSString *Img_id, *Img_name, *DynamicImgUrl;
Img_id = xmltag.equip_id;
Img_name = xmltag.image;
DynamicImgUrl = [NSString stringWithFormat:#"http://test.com/pics/equipment/%#/%#",Img_id, Img_name];
NSURL *ImageUrl = [NSURL URLWithString:DynamicImgUrl];
//UIImage *image = [UIImage imageWithData: [NSData dataWithContentsOfURL:ImageUrl]];
NSArray *tmp_array = [NSArray arrayWithContentsOfURL:ImageUrl];
for(NSString *str in tmp_array) {
[self.array addObject:str];
}
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES];
}
Am i correct here? how can i go with adding into the table view...
Kindly help me out...
U are seeking to display images on Tableview from URL, when u download image from url we need to consider some issues, it will freeze ur table view if u goahead with ordinary loading image.
i suggest u look at LazyLoading image
u see this link http://developer.apple.com/library/ios/#samplecode/LazyTableImages/Introduction/Intro.html
Download this sample and get to know the things