Lazy loading of image in tableview - iphone

Im trying to load the image of my uitableviewcells in lazy mode.
I'm trying to do it in the simplest possible way, I saw a lot of examples but they were going further than my purpose.
This is what Im doing currently, and its not working:
// Configure the cell...
Info *info = [self.Array objectAtIndex:indexPath.row];
cell.textLabel.text = info.name;
cell.detailTextLabel.text = info.platform;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
//The image is downloaded in asynchronous
NSBlockOperation *downloadingImgBlock = [NSBlockOperation blockOperationWithBlock:^{
NSString* imageURL = info.imgURL;
NSData* imageData = [[NSData alloc]initWithContentsOfURL:[NSURL URLWithString:imageURL]];
cell.imageView.image = [UIImage imageWithData:imageData];
}];
[self.queue addOperation:downloadingImgBlock];
Why it's not working? And how could it work?

Man, AsyncImageView is your friend! Just set the image url and everything is handled for you, downloading, caching. It's awesome.

Try the following codes
......
__block UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
__block UIImage *img;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
img = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString: imageURL]]];
dispatch_async(dispatch_get_main_queue(),^{
cell.imageView.image = img;
}
});
});
......

Using the 3rd party library source code would be easiest, but here's how you would do it in code. You are going to want to make a NSMutableArray either as a property in your .h or at the top of your .m like this:
#implementation viewController{
NSMutableArray *picList;
}
and in -initWithCoder: or, whatever init you are overloading:
picList = [[NSMutableArray alloc] init];
in – tableView:cellForRowAtIndexPath: (fullPicURL is the URL):
if([self imageExists:fullPicURL]){
shoePic = [picList objectForKey:fullSmallPicURL];
} else {
NSURL *picURL = [NSURL URLWithString:fullPicURL];
shoePic = [UIImage imageWithData:[NSData dataWithContentsOfURL:picURL]];
NSDictionary *thisImage = [NSDictionary dictionaryWithObject:shoePic forKey:fullSmallPicURL];
[cachedImages addEntriesFromDictionary:thisImage];
}
where -imageExists: is a function you write which checks the dictionary for the url. The object is the picture itself, the key is the url. Then you do cell.image = showPic;, or whatever you want to call it, and you're done for cacheing. In order to load them asynchronously, do this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:shoes];
[self performSelectorOnMainThread:#selector(fetchedShoeData:) withObject:data waitUntilDone:YES];
});
and at the end of fetchedData:
[self.tableView reloadData];
then just make sure you edit –numberOfSectionsInTableView: to change itself if it has to. Might be some other things you need to do to get it working 100%, let me know

try this one, I'm using always this scheme to load images asynchronously:
(I haven't found simplest way.)
- (void)inAnyMethod {
// ...
void (^blockLoadPhoto)() = ^{
UIImage *_imageTemporary = [UIImage imageNamed:#"..."];
if (_imageTemporary) {
[self performSelectorOnMainThread:#selector(setPhoto:) withObject:_imageTemporary waitUntilDone:true];
}
};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), blockLoadPhoto);
// ...
}
- (void)setPhoto:(UIImage *)photo {
// do something with the loaded image
}

I finally managed to do it setting the image with:
– performSelectorOnMainThread:withObject:waitUntilDone:
After the image is downloaded

Related

NSOperation and NSOperationQueue does not work

I have the following code and it does not work. Is there something working behind it.
[operationQueue addOperationWithBlock:^{
imageData = [NSData dataWithContentsOfURL:imageURL];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UIImage *image = nil;
if(imageData){
UIImage *image = [UIImage imageWithData:imageData];
cell.imageView.image = image;
}
}];
}];
Even I create a subclass of NSOperation and then alloc init it, it does not work the way I think it to. I always have to invoke start to the NSOperation subclass to run but I suppose sending start message to NSOperation runs it in the main thread rather than running in the background thread.
I want to add an alternative solution using GCD :
backgroundQueue = dispatch_queue_create("com.razeware.imagegrabber.bgqueue", NULL);
dispatch_async(backgroundQueue, ^{
/* put the codes which makes UI unresponsive like reading from network*/
imageData = [NSData dataWithContentsOfURL:imageURL];
..... ;
dispatch_async(dispatch_get_main_queue(),^{
/* do the UI related work on main thread */
UIImage *image = [UIImage imageWithData:imageData];
cell.imageView.image = image;
......; });
});
dispatch_release(backgroundQueue);
Let me know whether this one helped you ;)
Reference

Loading images one by one

I want to load images one by one not all together.In the following code imagedata is the array containing urls from which i need to load images. Here is my code but no success.
-(void)loadSingleImage:(int)buttonTag
{
UIButton *buttonImage =(UIButton *) [self.view viewWithTag:buttonTag];
NSData *imagesubCategoryData = [[NSData alloc]initWithContentsOfURL:[NSURL URLWithString:[imageData objectAtIndex:buttonTag-30]]];
[buttonImage setImage:[UIImage imageWithData:imagesubCategoryData] forState:UIControlStateNormal];
}
-(void)loadImageData
{
for(int i=0;i<[imageData count];i++)
{
[self loadSingleImage:i+30];
sleep(0.1);
}
}
You can use Grand Central Dispatch to load the images one by one..
Use the following code
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSURL *url = [NSURL URLWithString: #"http://www.gev.com/wp-content/uploads/2011/05/two_flowers.preview.jpg"];
dispatch_sync(dispatch_get_main_queue(), ^ {
[cell.cellImage setImageWithURL: url placeholderImage: [UIImage imageNamed: #"twitter.jpg"]];
});
});
It may helps you
You can use a NSTimer.. Let me known if you need sample code.
Do not use sleep(), instead use a runloop if you must delay:
NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop];
for(int i=0;i<[imageData count];i++)
{
[self loadSingleImage:i+30];
NSDate* dateLimit = [NSDate dateWithTimeIntervalSinceNow:0.1];
[currentRunLoop runUntilDate:dateLimit];
}
But a better solution is GCD.

UIImageView not showing up in custom UITableViewCell

Here is the code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"ConvoreCell";
ConvoreCell * cell = (ConvoreCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"ConvoreCell" owner:nil options:nil];
for (id currentObject in topLevelObjects){
if ([currentObject isKindOfClass:[UITableViewCell class]]){
cell = (ConvoreCell *) currentObject;
break;
}
}
}
NSMutableDictionary *cellValue = [results objectAtIndex:indexPath.row];
NSString *picURL = [[cellValue objectForKey:#"creator"] objectForKey:#"img"];
if ([picURL isEqualToString:#"https://secure.gravatar.com/avatar/1f043010eb1652b3fab3678167dc0487/?default=https%3A%2F%2Fconvore.com%2Fmedia%2Fimages%2Feric.png&s=80"])
picURL = #"https://convore.com/media/images/eric.png";
if ((picURL != (NSString *) [NSNull null]) && (picURL.length !=0)) {
NSLog(#"%#", picURL);
NSData *imgData = [[[NSData dataWithContentsOfURL:
[NSURL URLWithString:
picURL]] autorelease] retain];
[cell.pic initWithImage:[[UIImage alloc] initWithData:imgData]];
} else {
cell.pic = nil;
}
//NSLog(#"Name is : %#", [cellValue objectForKey:#"name"]);
cell.title.text = [cellValue objectForKey:#"name"];
cell.info.text = (#"Created by: %#", [[cellValue objectForKey:#"creator"] objectForKey:#"name"]);
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
// Configure the cell...
return cell;
}
For some reason the image is not showing up. What am I doing wrong here? The URL of the image is valid, I checked.
Also I don't want to have the accessory view on the TableViewCell, I did specify that as no in IB, but it still shows up... i set the accessory attribute to none already
you have some issues in your code.
NSData *imgData = [[[NSData dataWithContentsOfURL:
[NSURL URLWithString:
picURL]] autorelease] retain];
the former is not a real issue (maybe by coincidence?!), but it's ugly and confusing. get rid of the autorelease and the retain. The object you get from dataWithContentsOfURL: is already autoreleased.
[cell.pic initWithImage:[[UIImage alloc] initWithData:imgData]];
And your real issue might be this. From what I know you should call init exactly one time; after you've allocated an object.
So if cell.pic is an UIImageView I would try to change the code to something like this:
cell.pic.image = [[[UIImage alloc] initWithData:imgData] autorelease];
Once you see your images you should change your code so that it saves the images in the data you use to populate the cell. You are downloading the image every time a cell appears. That's something you shouldn't do. But this is beyond this question.
EDIT:
Also I don't want to have the
accessory view on the TableViewCell, I
did specify that as no in IB, but it
still shows up... i set the accessory
attribute to none already
You do? But I can see this line of code:
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
You should be able to get an UIImage by just
[cell.pic initWithImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSUrl NSURL picURL]]]];
You're not showing your cellForRowAtIndexPath method, which is the key to analyzing what's going on. The typical paradigm is that UITableViewCell objects get reused repeatedly by the UITableView. You should be setting the UIImage into the cell when the UITableView calls cellForRowAtIndexPath.
The problem causing your immediate crash is that the nib for ConvoreCell is trying to set an IBOutlet you have declared but finding no way to actually set it. I would check that you have synthesized your pic property.
There are other problems here. The line
[cell.pic initWithImage:[[UIImage alloc] initWithData:imgData]];
Is wrong; you do not call init on an object that already exists, but only when you are allocating an object. Do something like
cell.pic = [[[WhateverClassPicIS alloc] initWithImage:[[UIImage alloc] initWithData:imgData]];
Also, you are using NSData dataWithContentsOfURL on the main thread. This will lock up your user interface for an indeterminate time as you connect to the network. Use asynchronous calls instead.

Set UIImageView image using a url

Is it possible to read a url to an image and set a UIImageView to the image at this url?
For such a straightforward task I would highly recommend against integrating projects such as Three20, the library is a monster and not very straightforward to get up and running.
I would instead recommend this approach:
NSString *imageUrl = #"http://www.foo.com/myImage.jpg";
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:imageUrl]] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
myImageView.image = [UIImage imageWithData:data];
}];
EDIT for Swift 3.0
let urlString = "http://www.foo.com/myImage.jpg"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Failed fetching image:", error)
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
print("Not a proper HTTPURLResponse or statusCode")
return
}
DispatchQueue.main.async {
self.myImageView.image = UIImage(data: data!)
}
}.resume()
*EDIT for Swift 2.0
let request = NSURLRequest(URL: NSURL(string: urlString)!)
NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, error) -> Void in
if error != nil {
print("Failed to load image for url: \(urlString), error: \(error?.description)")
return
}
guard let httpResponse = response as? NSHTTPURLResponse else {
print("Not an NSHTTPURLResponse from loading url: \(urlString)")
return
}
if httpResponse.statusCode != 200 {
print("Bad response statusCode: \(httpResponse.statusCode) while loading url: \(urlString)")
return
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.myImageView.image = UIImage(data: data!)
})
}.resume()
NSString *ImageURL = #"YourURLHere";
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:ImageURL]];
imageView.image = [UIImage imageWithData:imageData];
It's possible to load an NSData from a URL and convert that to an image and stick that in the image view, but this is an extremely bad idea because it involves doing a synchronous URL download on the main thread. This will lock up the UI and possibly cause the user to think your app has crashed if the image doesn't download extremely fast.
Edit: To clarify, the original question looks to me like the poster wants to say something along the lines of
imageView.image = [UIImage imageWithContentsOfURL:theURL];
Hence my answer. It's possible to do the equivalent by going through NSData, but it's a bad idea. Instead, the image should be downloaded asynchronously using NSURLConnection, and only once it's fully downloaded should it be converted into a UIImage and assigned to the image view.
I am using https://github.com/rs/SDWebImage which is a beautifully designed library, which has the options to put a placeholder image, whether to memory, or disk cache the image or use the downloader independent of a UIImageView.
I was trying to do that by myself but the library took all the pain away.
All you need to do is to write the code below for your case :
#import "UIImageView+WebCache.h"
[imageView sd_setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"] placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
Worked like a charm for me.
If you want to display image from Url in ImageView, And also want to save this image in cache for optimize to server interaction this would help you Just pass your imageView object and string Url to this function
-(void)downloadingServerImageFromUrl:(UIImageView*)imgView AndUrl:(NSString*)strUrl{
strUrl = [strUrl encodeUrl];
NSString* theFileName = [NSString stringWithFormat:#"%#.png",[[strUrl lastPathComponent] stringByDeletingPathExtension]];
NSFileManager *fileManager =[NSFileManager defaultManager];
NSString *fileName = [NSHomeDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:#"tmp/%#",theFileName]];
imgView.backgroundColor = [UIColor darkGrayColor];
UIActivityIndicatorView *actView = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[imgView addSubview:actView];
[actView startAnimating];
CGSize boundsSize = imgView.bounds.size;
CGRect frameToCenter = actView.frame;
// center horizontally
if (frameToCenter.size.width < boundsSize.width)
frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
else
frameToCenter.origin.x = 0;
// center vertically
if (frameToCenter.size.height < boundsSize.height)
frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
else
frameToCenter.origin.y = 0;
actView.frame = frameToCenter;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSData *dataFromFile = nil;
NSData *dataFromUrl = nil;
dataFromFile = [fileManager contentsAtPath:fileName];
if(dataFromFile==nil){
dataFromUrl=[[[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:strUrl]] autorelease];
}
dispatch_sync(dispatch_get_main_queue(), ^{
if(dataFromFile!=nil){
imgView.image = [UIImage imageWithData:dataFromFile];
}else if(dataFromUrl!=nil){
imgView.image = [UIImage imageWithData:dataFromUrl];
NSString *fileName = [NSHomeDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:#"tmp/%#",theFileName]];
BOOL filecreationSuccess = [fileManager createFileAtPath:fileName contents:dataFromUrl attributes:nil];
if(filecreationSuccess == NO){
NSLog(#"Failed to create the html file");
}
}else{
imgView.image = [UIImage imageNamed:#"NO_Image.png"];
}
[actView removeFromSuperview];
[actView release];
[imgView setBackgroundColor:[UIColor clearColor]];
});
});
}
Here's an updated answer to the best answer, as imageWithContentsOfURL is no longer a method of UIImage. You'll have to use CIImage:
NSURL *url = [NSURL URLWithString:#"http://url_goes_here.com/logo.png"];
imageView.image = [UIImage imageWithCIImage:[CIImage imageWithContentsOfURL:url]];
Unfortunately this feature is not available as of this writing... instead you will have to implement the functionality yourself by:
Downloading the data of the image
Saving it or caching it somewhere (db or filesystem) and then
Setting the UIImaveView to the saved structure
Fortunately you don't have to break your head coming out with said functionality as Apple provides an example that does exactly that as part of their code samples.
Follow the code and I'm sure you will be able to accommodate it to your needs.
EGOImageLoading
EGOImageView* imageView = [[EGOImageView alloc] initWithPlaceholderImage:[UIImage imageNamed:#"placeholder.png"]];
imageView.frame = CGRectMake(0.0f, 0.0f, 36.0f, 36.0f);
//show the placeholder image instantly
[self.anyView addSubview:imageView];
[imageView release] //if you want
//load the image from url asynchronously with caching automagically
imageView.imageURL = [NSURL URLWithString:photoURL];
If you want more, there is a delegate to handle actions after loading
#protocol EGOImageViewDelegate<NSObject>
#optional
- (void)imageViewLoadedImage:(EGOImageView*)imageView;
- (void)imageViewFailedToLoadImage:(EGOImageView*)imageView error:(NSError*)error;
#end
Another option is TTImageView from the Three20 library which handles asynchronous loading of an image. The control works great. However, the downside is you have to deal with the nightmare of installing Three20 (which is then almost impossible to fully remove).
use AFNetworking category UIImageView+AFNetworking.h
#import "UIImageView+AFNetworking.h
[profileImageView setImageWithURL:[NSURL URLWithString:photoURL] placeholderImage:[UIImage imageNamed:#"placeholder"]];

Core data images from desktop to iphone

I built a simple mac data entry tool I use with an iPhone application. I've recently added thumbnail which I added via an Image Well using simple bindings. Its a transformable data type which seems to work fine.
The iPhone application however won't show the images. The attribute isn't null but I can't get an image to appear. The following is for cellForRowAtIndexPath
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
NSManagedObject *entity = nil;
if ([self.searchDisplayController isActive])
entity = [[self filteredListContent] objectAtIndex:[indexPath row]];
else
entity = [fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [entity valueForKey:#"name"];
//cell.imageview.image = [UIImage imageNamed:#"ImageB.jpeg"]; //works fine
cell.imageView.image = [entity valueForKey:#"thumbnail"];//no error, but no file
return cell;
I'm thinking either the problem is with the transformable (I'm using the default NSKeyedUnarchiveFromData), or how I'm calling the thumbnail. I'm a newbie so any help would be greatly appreciated.
Sounds like you are storing the image as an NSImage on the desktop and that object does not exist on the iPhone. Your desktop app needs to store the image in something that is portable, a PNG or JPG, etc. Then you will be able to load it back into your iPhone application as a UIImage.
Update re transformable
Sounds like you are still passing in a NSImage to the attribute and it is thinking you are handling it data. You need to convert it to a "standard" format first, like this:
NSBitmapImageRep *bits = [[myImage representations] objectAtIndex: 0];
NSData *data = [bits representationUsingType:NSPNGFileType properties:nil];
[myManagedObject setImage:data];
I recommend writing custom accessors to handle this, like the following:
#ifdef IPHONEOS_DEPLOYMENT_TARGET
- (void)setImage:(UIImage*)image
{
[self willChangeValueForKey:#"image"];
NSData *data = UIImagePNGRepresentation(image);
[myManagedObject setImage:data];
[self setPrimitiveValue:data forKey:#"image"];
[self didChangeValueForKey:#"image"];
}
- (UIImage*)image
{
[self willAccessValueForKey:#"image"];
UIImage *image = [UIImage imageWithData:[self primitiveValueForKey:#"image"];
[self didAccessValueForKey:#"image"];
return image;
}
#else
- (void)setImage:(NSImage*)image
{
[self willChangeValueForKey:#"image"];
NSBitmapImageRep *bits = [[image representations] objectAtIndex: 0];
NSData *data = [bits representationUsingType:NSPNGFileType properties:nil];
[myManagedObject setImage:data];
[self setPrimitiveValue:data forKey:#"image"];
[self didChangeValueForKey:#"image"];
}
- (NSImage*)image
{
[self willAccessValueForKey:#"image"];
NSImage *image = [[NSImage alloc] initWithData:[self primitiveValueForKey:#"image"]];
[self didAccessValueForKey:#"image"];
return [image autorelease];
}
#endif
This will give you a conditional compile and will store the data as NSData (in PNG format) that can be retrieved on any device.