guys, I have UITableView with some images, which are loading from internet. Images is very little, but my tableview isn't work perfect.
My code:
static NSString *CellIdentifier = #"Cell";
TDBadgedCell *cell = [[[TDBadgedCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cell.badgeColor = [UIColor colorWithRed:((float)128 / 255.0f) green:((float)161 / 255.0f) blue:((float)176 / 255.0f) alpha:1];
NSDictionary *dict = nil;
if (searching == NO) {
dict = [companies objectAtIndex:indexPath.row];
} else {
dict = [copyListOfItems objectAtIndex:indexPath.row];
}
cell.badgeString = [NSString stringWithFormat:#"%#",[dict objectForKey:#"rating"]];
cell.textLabel.numberOfLines = 2;
cell.textLabel.text = [dict objectForKey:#"title"];
cell.textLabel.textColor = [UIColor colorWithRed:((float)80 / 255.0f) green:((float)80 / 255.0f) blue:((float)80 / 255.0f) alpha:1];
cell.textLabel.font = [UIFont fontWithName:#"Verdana" size:17.0];
UIImage *img = [[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[dict objectForKey:#"image"]]]] scaleToFitSize:CGSizeMake(16, 16)];
cell.imageView.image = img;
[cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
return cell;
TDBadgedCell can't be reason, because without images it works perfect.
I'm using open sourse classes to resize UIImage. You can found it here.
Have you any ideas?
You could do the image downloading part in another thread so it won't block the main. You can do this like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul), ^{
// download the images here
dispatch_async(dispatch_get_main_queue(), ^{
// add them to your cell here
});
});
So you need to switch to another thread for downloading and then get back to the main thread and add them to the UI. UI elements must only be handled with in the main thread.
I have posted code that shows you to deal with this. We have a class MyImageDownloader that downloads an image on request and posts a notification when it arrives:
https://github.com/mattneub/Programming-iOS-4-Book-Examples/blob/master/p754p772downloader/p754downloader/MyImageDownloader.m
The table view data source supplies a placeholder if a MyImageDownloader has no image, and supplies the image if it has one:
https://github.com/mattneub/Programming-iOS-4-Book-Examples/blob/master/p754p772downloader/p754downloader/RootViewController.m
Thus, all we have to do is reload the table when an image arrives. m.
Related
I am using NSOperationQueue for caching images in background.
Here is the code below:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"CellIdentifier";
UITableViewCell *cell;
cell = [self.mUpcomEventsTable dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
UIImageView *imgView;
UILabel *lblEventName;
UILabel *lblDate;
UILabel *lblTime;
if(self.mEventNameArr != NULL)
{
NSString *imageUrlString = [NSString stringWithFormat:#"%#", [self.mEventImageArr objectAtIndex:indexPath.row]];
UIImage *cachedImage = [self.imageCache objectForKey:imageUrlString];
NSLog(#"cache:%#", self.imageCache);
imgView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 80, 90)];
lblEventName = [[UILabel alloc]initWithFrame:CGRectMake(90, 10, 200, 30)];
lblEventName.text = [self.mEventNameArr objectAtIndex: indexPath.row];
lblEventName.font = [UIFont fontWithName:#"Helvetica-Bold" size:18];
lblDate = [[UILabel alloc]initWithFrame:CGRectMake(90, 50, 100, 30)];
lblDate.text = [self.mEventDateArr objectAtIndex:indexPath.row];
lblTime = [[UILabel alloc]initWithFrame:CGRectMake(190, 50, 100, 30)];
lblTime.text = [self.mEventTimeArr objectAtIndex:indexPath.row];
strEventName = lblEventName.text;
strEventDate = lblDate.text;
strEventTime = lblTime.text;
if (cachedImage)
{
imgView.image = cachedImage;
}
else
{
// you'll want to initialize the image with some blank image as a placeholder
imgView.image = [UIImage imageNamed:#"Placeholder.png"];
// now download in the image in the background
NSLog(#"queue:%#",self.imageDownloadingQueue);
[self.imageDownloadingQueue addOperationWithBlock:^{
NSURL *imageUrl = [NSURL URLWithString:imageUrlString];
NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
UIImage *image = nil;
if (imageData)
image = [UIImage imageWithData:imageData];
if (image)
{
[self.imageCache setObject:image forKey:imageUrlString];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UITableViewCell *updateCell = [tableView cellForRowAtIndexPath:indexPath];
if (updateCell)
imgView.image = cachedImage;
}];
}
}];
}
}
else cell.textLabel.text = #"No event";
[cell addSubview:imgView];
[cell addSubview:lblEventName];
[cell addSubview:lblDate];
[cell addSubview:lblTime];
return cell;
}
It is not going in this line
[self.imageDownloadingQueue addOperationWithBlock:^{
it go outside from this. Why so, please help for above.
Took idea from here link.
There are a lot of disadvantage of using NSOperationQueue. One of them is your image will flick when every time you scroll the UITableView.
I'd suggest you to use this AsyncImageView. I've used it and it work wonders. To call this API:
ASyncImage *img_EventImag = alloc with frame;
NSURL *url = yourPhotoPath;
[img_EventImage loadImageFromURL:photoPath];
[self.view addSubView:img_EventImage]; // In your case you'll add in your TableViewCell.
It's same as using UIImageView. Easy and it does most of the things for you. AsyncImageView includes both a simple category on UIImageView for loading and displaying images asynchronously on iOS so that they do not lock up the UI, and a UIImageView subclass for more advanced features. AsyncImageView works with URLs so it can be used with either local or remote files.
Loaded/downloaded images are cached in memory and are automatically cleaned up in the event of a memory warning. The AsyncImageView operates independently of the UIImage cache, but by default any images located in the root of the application bundle will be stored in the UIImage cache instead, avoiding any duplication of cached images.
The library can also be used to load and cache images independently of a UIImageView as it provides direct access to the underlying loading and caching classes.
I have a table view loading in the below manner. If a condition is met I need to add a particular image above the cell.imageview. Also the images are coming in different dimensions. Below is my code can anybody point me in where i am going wrong.
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
if(array==nil||[array count]==0)
{
}
else
{
NSMutableDictionary *dicttable=[array objectAtIndex:indexPath.row];
NSString *head=[dicttable objectForKey:#"name"];
NSString *type=[dicttable objectForKey:#"type"];
NSString *imgUrl = [dicttable objectForKey:#"image"];;
if(imgUrl!=nil)
{
if(![[ImageCache sharedImageCache] hasImageWithKey:imgUrl])
{
cell.imageView.image = [UIImage imageNamed:#"noimage_icon.png"];
NSArray *myArray = [NSArray arrayWithObjects:cell.imageView,imgUrl,#"noimage_icon.png",[NSNumber numberWithBool:NO],nil];
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate performSelectorInBackground:#selector(updateImageViewInBackground:) withObject:myArray];
cell.imageView.frame=CGRectMake(0,0,48,48);
cell.imageView.bounds=CGRectMake(0,0,48,48);
[cell.imageView setClipsToBounds:NO];
}
else
{
cell.imageView.image = [[ImageCache sharedImageCache] getImagefromCacheOrUrl:imgUrl];
cell.imageView.frame=CGRectMake(0,0,48,48);
cell.imageView.bounds=CGRectMake(0,0,48,48);
[cell.imageView setClipsToBounds:NO];
}
}
else
{
cell.imageView.image = [UIImage imageNamed:#"noimage_icon.png"];
}
if([type isEqualToString:#"YES"])
{
UIImageView* img = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"bluel.png"]];
[cell setBackgroundView:img];
[img release];
cell.textLabel.text = head;
cell.textLabel.backgroundColor = [UIColor clearColor];
cell.detailTextLabel.textColor=[UIColor grayColor];
cell.detailTextLabel.text = subtitle1;
cell.detailTextLabel.backgroundColor = [UIColor clearColor];
}
else
{
UIImageView* img = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"wnew1.png"]];
[cell setBackgroundView:img];
[img release];
cell.textLabel.text = head;
cell.textLabel.backgroundColor = [UIColor clearColor];
[cell.imageView addsubview:spclimage]
cell.textLabel.text = head;
cell.textLabel.backgroundColor = [UIColor clearColor];
cell.detailTextLabel.textColor=[UIColor grayColor];
cell.detailTextLabel.text = subtitle1;
cell.detailTextLabel.backgroundColor = [UIColor clearColor];
}
}
return cell;
Here the problem is only in the last row the special image is adding. Not in all rows. Also the image view size is different in all the time the tableview is reloading?
A couple of thoughts:
The updateImageViewInBackground seems suspect, as the name suggests that you're updating an image view, but you don't specify which cell you're updating.
I also see you doing a addSubview:spclimage. Obviously, if that spclimage was on another cell, as soon as you do addSubview, it will be removed from the previous location before being added to current cell. In fact, just the notion of adding an image as a subview of an existing imageview is curious.
If your cache doesn't have the image yet, I see where you're initializing the image with noimage_icon.png, but I don't see where you're actually updating the image view. You say updateImageViewInBackground is "then updating the image". Do you mean setting the image property for the imageView for this cell for this indexPath? Or maybe updating spclimage? If so, that's problematic.
The typical pattern (using GCD) for this would be:
cell.imageView.image = [UIImage imageNamed:#"noimage_icon.png"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = ... // do whatever you need to do to get the image, load cache, etc.
// ok, now that you have the image, dispatch the update of the UI back to the main queue
dispatch_async(dispatch_get_main_queue(), ^{
// because we're doing this asynchronously, make sure the cell is still
// visible (it could have scrolled off and the cell was dequeued and
// reused), so we're going to ask the tableview for the cell for that
// indexPath, and it returns `nil` if it's not visible. This method is
// not to be confused with the similarly named `UITableViewControllerDelegate`
// method.
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
// if the image view is still visible, update it
if (cell)
{
cell.imageView.image = image;
}
});
});
I'm using the xml for fetching the data and display it in the uitable. In the XML i have a separate tag for the image as "link". I'm displaying this image as the cell image. Everything workings fine when the applications loads, but when i scroll the table the images in the cell are getting changed.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
cell.accessoryType=UITableViewCellAccessoryNone;
cell.selectionStyle=UITableViewCellSelectionStyleNone;
//cell. separatorStyle=UITableViewCellSeparatorStyleNone;
titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(110, 10, 190, 130)];
[titleLabel setTag:2];
titleLabel.numberOfLines=7;
[titleLabel setFont:[UIFont systemFontOfSize:14]];
[titleLabel setTextColor:[UIColor blackColor]];
[titleLabel setBackgroundColor:[UIColor clearColor]];
[cell.contentView addSubview:titleLabel];
[titleLabel release];
imageView= [[UIImageView alloc] initWithFrame:CGRectMake(5, 20, 90, 110)];
[cell.contentView addSubview:imageView];
}
NSDictionary *appRecord = [responseArr objectAtIndex:indexPath.row];
NSString *urlLink;
urlLink=[NSString stringWithFormat:#"%#",[appRecord objectForKey:#"link"]];
NSLog(#"LINK : %#",urlLink);
if([urlLink length] ==0)
{
imageView.image=[UIImage imageNamed:#"ioffer.png"];
}
else {
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:urlLink]];
UIImage *image = [[UIImage alloc] initWithData:imageData];
imageView.image = image;
}
UILabel *desclbl = (UILabel*)[cell.contentView viewWithTag:2];
desclbl.text = [NSString stringWithFormat:#"%#",[appRecord objectForKey:#"description"]];
return cell;
}
I have used these code in the table's cellForRowAtIndexPath. responseArr is the array where i stored the links.
Can any one help me in this issue.
Thanks in advance
Malathi
If you reuse an existing table view cell, i.e. the code skips the first if-block, then imageView isn't initialized (where is it declared anyway?). That means even though you set a new text, some random image is reused.
The fix is to make sure that imageView is intialized in all cases.
And please do declare it locally in this method. Everything else seems very dangerous to me.
The "implied else" of the if (cell == nil) is that it's reusing a cell from the table's cache. It doesn't reset anything about those, it just picks them up and uses them. Which means that any element you've got on there needs to be cleared manually, especially if it's going to be some time before they're filled in.
You're already doing some defaulting, here:
if([urlLink length] ==0)
{
imageView.image=[UIImage imageNamed:#"ioffer.png"];
}
If you just make that NOT conditional -- ie just say
imageView.image=[UIImage imageNamed:#"ioffer.png"];
what you'll get is, your cell will load with its default image, then when the image loads remotely it'll replace it.
NOW: You're doing that image load synchronously. Which means your whole UI is going to freeze while the web request is being made. Not great.
I had the same issue. The cells are reused at an other index, when scrolling.
Try putting the code, that fills the cells with data, outside the "if (cell == nil)". Worked for me.
Best regards,
Alexander
You also use the custom cell instead of this and check each time
as like given below example-
Condition-
if(shout.creator.avatarImage){
avatarView.image = shout.creator.avatarImage;
}
else{
FetchImageOperation *operation = [[FetchImageOperation alloc] init];
operation.delegate = self;
operation.urlString = shout.creator.avatarURL;
[operationQueue addOperation:operation];
[operation release];
}
-(void)fetchthumbImageOperation:(FetchThumbImageOperation *)operation didFetchImage:(UIImage *)image
{
avatarView.image= image;
}
Here, i check the condition for image in tableview data source.
-(UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
}
Hey all, I'm trying to create a business finder style app and was hoping to create a custom cell to display some information to the user. I've got the cell to display all the information I'm trying to give, but the format is off. Here is the code I'm using to create the cell.
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"CustomCell";
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomCellView" owner:self options:nil];
#ifdef __IPHONE_2_1
cell = (CustomCell *)[nib objectAtIndex:0];
#else
cell = (CustomCell *)[nib objectAtIndex:1];
#endif
}
// Configure the cell.
NSDictionary *dict = [rows objectAtIndex: indexPath.row];
cell.bizNameLabel.text = [dict objectForKey:#"name"];
cell.addressLabel.text = [dict objectForKey:#"address"];
NSMutableString *detailed = [NSMutableString string];
[detailed appendString:[dict objectForKey:#"distance"]];
[detailed appendString:#"mi"];
cell.mileageLabel.text = detailed;
// determine the rating for the business
NSString *icontype = [dict objectForKey:#"rating"];
NSInteger rating = [icontype intValue];
UIImage *image;
// variable to change the color of the cell's background
UIView *bg = [[UIView alloc] initWithFrame:cell.frame];
// switch statement to determine the rating image to be displayed
switch (rating) {
case 1:
{
image = [UIImage imageNamed:#"1.png"];
bg.backgroundColor = [UIColor yellowColor];
break;
}
case 3:
{
image = [UIImage imageNamed:#"3.png"];
bg.backgroundColor = [UIColor orangeColor];
break;
}
case 5:
{
image = [UIImage imageNamed:#"5.png"];
//bg.backgroundColor = [UIColor redColor];
cell.backgroundColor = [UIColor redColor];
break;
}
default:
break;
}
[cell.ratingImage setImage:image];
cell.backgroundView = bg;
[bg release];
#ifdef __IPHONE_3_0
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
#endif
return cell;
}
I then created the layout in IB to look like this..
(First 2 issues have been resolved, I was using the wrong image variable to display the image)
And when selected, you can tell things are out of whack
![enter image description here][3]
In IB I have the dimensions of the cell set at 320wide by 80high, and under the indentity tab, I've changed the class to CustomClass. I'm sure I'm overlooking something trivial, but if someone could throw me a bone, I'd be grateful. I've fixed the problem I was having with the image not displaying where I wanted it to, but I'm still having issues with the background color not changing, font size displaying different and when the cell is selected, it overlaps the cell separator.
Note: tried to include screen shots, but since I'm new SO wouldn't let me. I've provided a link where I've put the image of the selected cell that overlaps the separator below.
http://s1216.photobucket.com/albums/dd361/cabearsfan/?action=view¤t=screenshot2.png
Seems like you are not using your nib reference of the UIImageView but you are using the default imageView property of the cell.
I'd say there is a mis-match between the imageview frame and the size of the image you're giving it.
After your setImage call, add this code to find out what's going on:
NSLog(#" imageView frame %f,%f, %f, %f",imageView.frame.origin.x,imageView.frame.origin.y,
imageView.frame.size.width,imageView.frame.size.height);
NSLog(#" image size %f x %f", image.size.width, image.size.height);
I'm using the method that Apple shows on using subviews in table (most of what is below is from their documentation). I am only pulling in via an rss feed about 12 images, but this results in slow scrolling - if I get rid of the images it moves smoothly. The images are not big, so that can't be the problem. Before looking into more involved solutions (background processing, etc.), is there anything I can do to make this work better?
Thanks for any help you can give on this.
#define MAINLABEL_TAG 1
#define SECONDLABEL_TAG 2
#define PHOTO_TAG 3
-(UITableViewCell *)tableView : (UITableView *)tableView cellForRowAtIndexPath : (NSIndexPath *)indexPath {
UILabel * mainLabel, * secondLabel;
UIImageView * photo;
static NSString * CellIdentifier = # "Cell";
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
mainLabel = [[[UILabel alloc] initWithFrame:CGRectMake(100.0, 0.0, 210.0, 0.0)] autorelease];
mainLabel.tag = MAINLABEL_TAG;
mainLabel.font = [UIFont systemFontOfSize:14.0];
mainLabel.textAlignment = UITextAlignmentRight;
mainLabel.textColor = [UIColor blackColor];
mainLabel.opaque = YES;
mainLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
[cell.contentView addSubview : mainLabel];
secondLabel = [[[UILabel alloc] initWithFrame:CGRectMake(90.0, 30.0, 220.0, 0.0)] autorelease];
secondLabel.tag = SECONDLABEL_TAG;
secondLabel.font = [UIFont systemFontOfSize:12.0];
secondLabel.textAlignment = UITextAlignmentRight;
secondLabel.textColor = [UIColor darkGrayColor];
secondLabel.opaque = YES;
secondLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
[cell.contentView addSubview : secondLabel];
photo = [[[UIImageView alloc] initWithFrame:CGRectMake(30.0, 3.0, 50.0, 40.0)] autorelease];
photo.tag = PHOTO_TAG;
photo.opaque = YES;
photo.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
[cell.contentView addSubview : photo];
} else {
mainLabel = (UILabel *)[cell.contentView viewWithTag : MAINLABEL_TAG];
secondLabel = (UILabel *)[cell.contentView viewWithTag : SECONDLABEL_TAG];
photo = (UIImageView *)[cell.contentView viewWithTag : PHOTO_TAG];
}
// Configure the cell.
mainLabel.text = [[items objectAtIndex:indexPath.row] objectForKey:# "title"];
secondLabel.text = [[items objectAtIndex:indexPath.row] objectForKey:# "teacher"];
NSString * path = [[items objectAtIndex:indexPath.row] objectForKey:# "audimage"];
NSString * mypath = [path stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSURL * url = [NSURL URLWithString:mypath];
NSData * data = [NSData dataWithContentsOfURL:url];
UIImage * img = [[UIImage alloc] initWithData:data];
photo.image = img;
return cell;
[cell release];
}
It looks like every time you're configuring a cell, you re-download the entire image from the web (or reading from the hard disk, if that's where your URL points to):
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *img = [[UIImage alloc] initWithData:data];
This is really slow! The way UITableView works is by reusing cells - every time a cell goes off-screen, it may be given back to the cellForRowAtIndexPath method to be used as the next cell that becomes visible.
This means that every time a new cell becomes visible, you're downloading and creating its image.
Instead, you have two options:
Download and store all the images first (if there are only 12, it probably won't take too much memory).
Download the images as-needed, and if you get a memory warning, dump some of the cached images. This is a bit harder to implement, so I'd try the first solution first.