I've got a custom UITableViewCell class whose model object performs an asynchronous download of an image which is to be displayed in the cell. I know I've got the outlets connected properly in IB for WidgetTVC, I know that image data is being properly returned from my server, and I've alloc/init'd the widget.logo UIImage too. Why is the image always blank then in my tableViewCell? Thanks in advance.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
Widget *theWidget = [widgetArray objectAtIndex:indexPath.row];
static NSString *CellIdentifier = #"WidgetCell";
WidgetTVC *cell = (WidgetTVC*)[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
[[NSBundle mainBundle] loadNibNamed:#"WidgetTVC" owner:self options:nil];
cell = self.widgetTVC;
self.widgetTVC = nil;
}
[cell configureWithWidget:theWidget];
return cell;
}
In my WidgetTVC class, I have the following method:
- (void)configureWithWidget:(Widget*)aWidget {
self.widget = aWidget;
self.nameLbl.text = aWidget.name;
[self.logoIvw setImage:aWidget.logo]; // logoIvw is the image view for the logo
}
Finally- I've got the callback method that sets the logo UIImage property on the Widget model object asynchronously (simplified):
(void)didReceiveImage:(ASIHTTPRequest *)request {
// I pass a ref to the Widget's logo UIImage in the userInfo dict
UIImage *anImage = (UIImage*)[request.userInfo objectForKey:#"image"];
UIImage *newImage = [UIImage imageWithData:[request responseData]];
anImage = newImage;
}
In didReceiveImage:, you're only modifying the local pointer anImage. You need to set the image property of your UIImageView in order to update what gets displayed. Instead of stashing a reference to your widget's UIImage, pass a reference to the UIImageView, and in didReceiveImage: do something like
UIImageView *imageView = (UIImageView*)[request.userInfo objectForKey:#"imageView"];
UIImage *newImage = [UIImage imageWithData:[request responseData]];
imageView.image = newImage;
Perhaps the best solution would be to have your model object have the image as a property and the display object subscribe to the image property's changes through KVO and update itself whenever the image property changes.
OK- there were a couple things wrong here. Thanks to bosmacs and Ed Marty as both of your comments were used to get to the solution.
First, I added a method to the Widget object to get the logo asynchronously:
- (void)asyncImageLoad {
...
// logo is a UIImage
[AsyncImageFetch fetchImage:&logo fromURL:url];
}
And my own AsyncImageFetch class looks like this:
+ (void)fetchImage:(UIImage**)anImagePtr fromURL:(NSURL*)aUrl {
ASIHTTPRequest *imageRequest = [ASIHTTPRequest requestWithURL:aUrl];
imageRequest.userInfo = [NSDictionary dictionaryWithObject:[NSValue valueWithPointer:anImagePtr] forKey:#"imagePtr"];
imageRequest.delegate = self;
[imageRequest setDidFinishSelector:#selector(didReceiveImage:)];
[imageRequest setDidFailSelector:#selector(didNotReceiveImage:)];
[imageRequest startAsynchronous];
}
+ (void)didReceiveImage:(ASIHTTPRequest *)request {
UIImage **anImagePtr = [(NSValue*)[request.userInfo objectForKey:#"imagePtr"] pointerValue];
UIImage *newImage = [[UIImage imageWithData:[request responseData]] retain];
*anImagePtr = newImage;
}
Finally, per Ed, I added this to the configureWithWidget method that helps set up my WidgetTVC:
[aCoupon addObserver:self forKeyPath:#"logo" options:0 context:nil];
And when a change is observed, I update the imageView and call [self setNeedsDisplay]. Works like a charm. Any way I can give you both points?
Related
I have a code to show image from remote URL.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *imageData = nil;
imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:image]];
if (imageData){
dispatch_async(dispatch_get_main_queue(), ^{
// UIKit, which includes UIImage warns about not being thread safe
// So we switch to main thread to instantiate image
UIImage *image1 = [UIImage imageWithData:imageData];
self.imageLabel.image = image1;
});
}
});
But it takes 2 seconds to download the image. I tried to put animated loading image to the UIImageView, but not worked. How can I implement a loading image for that 2 second?
Usually there is no built in method in iOS to provide a placeholder image. You can accomplish this task using a framework called SDWebImage.framework. In one of my projects I had to display placeholder images while the main images are loading from the server. I used the same framework and displayed images using UICollectionView, the code I used is:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CollectionCell *Cell = [collectionView
dequeueReusableCellWithReuseIdentifier:#"MyCell"
forIndexPath:indexPath];
[Cell.imageview setImageWithURL:url placeholderImage:[UIImage imageNamed:#"image.png"]];
return myCell;
}
This piece gives strange results in my TableView. I want the UIImage displayed for the cells where the reprecented objects value for key "Marked" = "Yes".
What is the correct code for this?
if ([[[sortedObjects objectAtIndex: indexPath.row] valueForKey:#"Marked"] isEqual:#"Yes"])
{
cell.markedImageView.image = [UIImage imageNamed:#"markedItem.png"];
}
The problem is that you need to clear the imageView image each time the cell is reused.
- (void)configCell:(MyCustomCell *)cell atIndexPath:(NSIndexPath)indexPath
{
cell.markedImageView.image = nil;
// configure cell normally
if ([[[sortedObjects objectAtIndex: indexPath.row] valueForKey:#"Marked"] isEqual:#"Yes"])
{
cell.markedImageView.image = [UIImage imageNamed:#"markedItem.png"];
}
}
I have a UITableViewCell that contains 4 photos and i get these photos from the web but the problem is when i scroll down the UITableView these photos are downloaded again
And this is the code:
ITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cellPhoto"];
if (cell == nil) {
NSArray *nibObject = [[NSBundle mainBundle] loadNibNamed:#"CustomCellThumbnails" owner:self options:nil];
cell = [nibObject objectAtIndex:0];
}
// Get the photos
int getPhotos = indexPath.row * 4;
for (int i = 1; i <= 4; i++) {
if (getPhotos < [imageArray count])
{
UIButton *imageButton = (UIButton*)[cell viewWithTag:i];
NSString *url = [imageArray objectAtIndex:getPhotos];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://www.siteweb.com%#",url]]];
UIImage *imageFieldProfile = [[UIImage alloc] initWithData:imageData];
dispatch_async(dispatch_get_main_queue(), ^{
// Set the phoyo to the UIButton
[imageButton setBackgroundImage:imageFieldProfile forState:UIControlStateNormal];
[imageFieldProfile release];
// Set the corner of UIButton
[imageButton.layer setCornerRadius:5.0];
[imageButton.layer setMasksToBounds:YES];
imageButton.tag = getPhotos;
});
});
[imageButton addTarget:self action:#selector(displayPhoto:) forControlEvents:UIControlEventTouchUpInside];
}
getPhotos ++;
}
You should use the view controller to fill the cells, not the UITableViewCell. If you do that, it's not only a better coding style, it's also easier to save the data.
Anyway, if you really must: initialize the UITableViewCell with some kind of storage table, so that you can store the data you downloaded: rewrite initWithStyle:reuseIdentifier: to initWithStyle:reuseIdentifier:usingCacheTable:
But, again, the correct way to do this is to load the data in the view controller and let the UIView subclasses simply only show stuff.
You should save or cache the images in another object when they are downloaded, not in the table view cell. Perhaps using some sort of data source or model object from which the table view cell requests the images, instead of having the table view cell directly make any URL requests. Then the model object can cache images after downloading and before handing them to the cell.
You could use a combination of lazy loading image views in combination with local caching. This would be rather easy to accomplish using ASIHTTPRequest in combination with correctly setup caching flags.
ASIHTTPRequest is extremely easy to use and its caching is very well controllable.
In contrast to the other solutions suggested, I would stick to use UITableView and its UITableViewCells as this will allow you to use de/queued cells without having to build such logic yourself.
I have used such solution for a major newsmagazine app (over 2mio downloads) and am totally satisfied with the results.
I am creating an application which uses a web service.And retrieves a list of users and there details like images, user id and there names.I displayed all the information related to users in table view, Thus each of the cell in the table view has an image with the for tables in it. I am using a Custom cell class to create the individual cells. selecting any row in table presents a detail view (using navigation) which show a larger image and all the details related to the particular user i selected.
in customcell and detail view class i am using web view to display image.
But when i run the app the images gets a bit of delay to display and same happens with the detail view.
Is there any alternative so that i can improve the performance of the table view so that i can have smooth scrolling of table view with out any delay in image loading and detail view??
here is the code...
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CustomCellIdentifier = #"CustomCellIdentifier ";
CustomCell *cell = (CustomCell *)[tableView
dequeueReusableCellWithIdentifier: CustomCellIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomCell" owner:self options:nil ];
//cell = [[CustomCell alloc] initwi ];
for (id oneObject in nib)
if ([oneObject isKindOfClass:[CustomCell class]])
cell = (CustomCell *)oneObject;
}
NSUInteger row = [indexPath row];
NSString *imageName = [imgArray objectAtIndex:row];
NSString *completeImageUrl = [[NSString alloc] initWithFormat:#"http://122.160.153.166:201/%#", imageName];
NSURL *url = [NSURL URLWithString:completeImageUrl];
//NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
NSData *imageData = [[NSData alloc] initWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:imageData]; /// check to see if we are getting all the arrays such as image array and userId array and name array of same size..
if(image == nil)
{
}
cell.imgView.image = image; /// other wise an execption of out out array range will be shown
[image release];
[imageName release];
//[cell.webView initWithFrame:CGRectZero];
//[cell.webView loadRequest:requestObj];
//cell.webView.frame = CGRectMake(20, 0, 80.0, 64);
cell.userIdLabel.text = (NSString *)[userId objectAtIndex:row];
cell.nameLabel.text = (NSString *)[userName objectAtIndex:row];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}`
i think problem can be in imgArray array as i am setting it from other class. Where i request the web service and fetched all the data about users .
Any help is greatly appreciated . Thanks in advance
I got the same problem. For that I used EGOImageCache and some thing.
Find this Url for EGOImage
Download files those names starts with "EGO", and add those to your project.
and write the following code where ever you want to put the image:
EGOImageView *imgView = [[EGOImageView alloc] initWithFrame:CGRectMake(50,50,220,360)];
NSString *imageName = [imgArray objectAtIndex:row];
NSString *completeImageUrl = [[NSString alloc] initWithFormat:#"http://122.160.153.166:201/%#", imageName];
NSURL *url = [NSURL URLWithString:completeImageUrl];
imgView.imageUrl = url;
[self.view addSubView:imgView];
Thats it. Use the similar code, where ever you want the image.
At the first time while loading it will take some time to download the image. But later it will cache the image and use it the next time.
Is there any alternative so that i can improve the performance of the table view so that i can have smooth scrolling of table view with out any delay in image loading
For this the answer is Lazy Loading of images in cells
You can implement lazy loading on webviewDidLoad which shows that webview has loaded completely
Using a UIWebView to display a single image is overkill. You should be using NSURLConnection (or one of many alternative HTTP wrappers/libraries) to load the image data and UIImageView to display it in each of your table cells. In my experience, there is no way (or at least no straightforward way) to eliminate the rendering delay when using UIWebView.
Why are you using the webview for displaying the images. Imageview should be used instead. If you are getting the images from the server then you should get those in separate thread and after you receive the images you should reload that particular row.
Some code snippet is like this:
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath
...
...
UIImage *img = (UIImage *)[imagesArray objectAtIndex:indexPath.row];
if(img == nil){
[imgView setImage:temporaryImage];
[NSThread detachNewThreadSelector:#selector(getTheThumnbails:) toTarget:self withObject:indexPath];
}
else
[imgView setImage:img];
for further assistance look at this
Hope this helps.
Thanks,
Madhup
okay finally i removed the uiweb view from my app and used Imaged view and succeed.
i used the link that was given by the developer of the tweetie . here it is link text
i used the ABTableViewCell class and created the whole cell using the code.
The scrolling was very jerky when i executed the app. after applying almost 4 hours and with the help of lazy loading i was able to run my app smoothly. I stored the data which is retrieved from url into an array then applied the concept in tableView:cellForRowAtIndexPath:
method..
Thanks you guys for helping me ...
Three20 has a rich-text table view cell class (TTStyledTextTableCell I think) - should render significantly faster than an embedded UIWebView. You could also roll your own, though that would take a lot longer.
I use datamodel to store 2 objects : Video, Images.
Video contain just string attributes and Images have 2 "Binary data" attributes.
At the start the 2 binary data attributes was in the video object.
But all videos are loading during initialization of UITableView.
For 400 videos binary data represent 20 Mo, so imagine with 4000 videos...
Now with 2 objects the UITableView loading work well.
I load binary data when it's necessary in the method : tableView:cellForRowAtIndexPath
But now more I scroll into the list, more the memory grow up :(
look at my method :
- (UITableViewCell *)tableView:(UITableView *)myTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"videoCell";
Video *theVideo = (Video *)[[self fetchedResultsController] objectAtIndexPath:indexPath];
VideoCellViewController *cell = (VideoCellViewController *)[myTableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"VideoCellView" owner:self options:nil];
cell = editingTableViewCell;
self.editingTableViewCell = nil;
}
cell.video = theVideo;
return cell;
}
And the method setvideo in VideoCellViewController
- (void)setVideo:(Video *)newVideo {
if (newVideo != video) {
[video release];
video = [newVideo retain];
}
NSData *imageData = [video.allImages valueForKey:#"thumbnailImage"];
UIImage *uiImage = [[UIImage alloc] initWithData:imageData];
smallImage.image = uiImage;
nameLabel.text = video.displayName;
[uiImage release];
}
Even without set the smallImage, I have memory trouble.
If I load the image object, it's never release.
I try a lot of solution to release memory without succes...( didTurnIntoFault, release, CFRelease...)
In performance tool, I can see my binary data as CFData.
I use a lot iPhoneCoreDataRecipes and PhotoLocations sample.
I need help to clean my memory ;)
Thanks
Samuel
Clearly there is something going on with your table cell creation logic. Let's take a look at a typical cellForRow delegate handler first..
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];
}
// do stuff with cell
return cell;
Here we see we are
trying to get a reusable cell
if that fails (nil) create a new one and pass the reusable id to the ctor
then do stuff with the cell (new or existing) and return it
If you do not key the cell for reuse in the table view, you will always get a 'nil' cell returned from the dequeue, hence the need to create new cells every time. This will cause memory to continue to grow as you scroll around, but stay fairly flat when idle.
EDIT:
Assuming your cell is fine, then you need to narrow down if it's the video data or the image data that is leaking. What is smallImage? And are you sure you do not want to do everything only when the video is new?
- (void)setVideo:(Video *)newVideo {
if (newVideo != video) {
[video release];
video = [newVideo retain];
NSData *imageData = [video.allImages valueForKey:#"thumbnailImage"];
UIImage *uiImage = [[UIImage alloc] initWithData:imageData];
smallImage.image = uiImage;
nameLabel.text = video.displayName;
[uiImage release];
}
}