I am trying to download some thumbnail from server and then display them in each cell :
// displaying image...
dictionary = [newsFeed objectAtIndex:indexPath.row];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[dictionary objectForKey:#"image"]]]];
cell.imageView.image = [image imageScaledToSize:CGSizeMake(55, 55)];
but after downloading and displaying images, the table view scrolls slowly!
The main problem is that [NSData dataWithContentsOfURL:] method is a synchronous request. It blocks the thread where is running until the request is done.
You should avoid this type of interaction if you run this on the main thread. This thread will be frozen and the user will be disappointed ;)
You have many solutions for this. A simple one is to use third library like SDWebImage or AFNetworking.
Both have categories around UIImageView class that allows to use them in a simple manner. For example, using UIImageView+AFNetworking you should do like the following:
[cell.imageView setImageWithURL:yourURL placeholderImage:yourPlaceholderImage];
I think You are trying to say this.
NSURL* url = [NSURL URLWithString:#"http://www.YourImageUrl.com"];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response,
NSData * data,
NSError * error) {
if (!error){
UIImage* image = [[UIImage alloc] initWithData:data];
// Now workout with the image
}
}];
This will make the asynchronous call, and load the images after tableview loaded i.e when the image load complete the image will show but the table will be loaded when the table view is needed to load.
Directly use like this
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[dictionary objectForKey:#"image"]]]];
It takes the main thread, so you can't scroll befor download your Image.So You add NSURLConnectionDataDelegate, NSURLConnectionDelegate add this protocol and download image by
Insert these lines of coding where you want to download Your Image
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
received_data = [[NSMutableData alloc]init];
[connection start];
These lines triggers these delegates to download data.
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[received_data setLength:0];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[received_data appendData:data];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//Use your download image
imageView.image = [UIImage imageWithData:received_data];
}
Using AFNetworking will get you a better feel. There is a UIImageView category as part of the project that will insert a temporary image, while switching to a background thread to download the real image.
That will keep the image download from locking the main thread. Once the image is downloaded the temporary image will be swapped out for you.
Here's a nice tutorial that includes some pointers on using the UIImageView category AFNetworking crash course.
I have written an answer here:
https://stackoverflow.com/a/14624875/1375695
which explains how to use Grand Central Despatch to keep your table scrolling smoothly while your images are downloading in the background. That answer doesn't mandate the use of a 3rd party library. Although - as Andrew and Flexaddicted have mentioned - there are libraries which will provide a solution for you - it might be worth reading so that you can understand what needs to happen under the hood.
Note that in my proposed solution, you could still use your synchronous method to actually get the data from the network:
UIImage *image = [UIImage imageWithData:
[NSData dataWithContentsOfURL:
[NSURL URLWithString:
[dictionary objectForKey:#"image"]]]];
as it would be called on an asynchronous background thread.
I highly recommend the AFNetworking framework as #flexaddicted has recommended (UIImageView+AFNetworking). Also, check out the Sensible TableView framework. It should be able to automatically fetch all data from the web service and display them in the table view, and will also automatically handle all asynch image downloading. Should save you a bunch of work.
For More convenience to download image use EGOImageLoader/EGOImageView
Download this class by this link
https://github.com/enormego/EGOImageLoading
Related
An app that I am making will contain mass amounts of images. So obviously it would be really bad to store them all on the users phone and take up all the disk space. As I am new to iOS Development, how would I best go about loading an image remotely into a UIImageView. What are my options?
Just to let you know I would only ever be loading a maximum of 2 images at a time, if it helps.
Any advise would be very much appreciated. Thank you in advance!
You can download image Asynchronously by unblocking the main thread using the below code
NSURLRequest* request=[NSURLRequest requestWithURL:[NSURL URLWithString:#"Your imageURL"]];
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc]init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if (error==nil && [data length]>0)
{
UIImage* img=[UIImage imageWithData:data];
// set the downloaded image to imageview here
}
else
{
NSLog(#"%#",error);
}
});
Use SDImageView or similar open sources, another options if you are loading images in table, use Lazy Loading by Apple
I have been trying to display large image from server, but I have to display it progressively.
I used subclass of UIView and in that I have taken UIImage object, in which I used NSURLConnection and its delegate methods, I also used
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
in which I am appending data and converting it to UIImage object, and drawing rect using the drawInRect: method of UIImage.
Everything is working fine, but the problem is, when image is being drawn on context, I cannot click anywhere else on screen until entire image is being drawn on to screen.
Is there any good solution, where I can click anywhere else even if image is being drawn on screen?
Any help will be appreciable.
Edit:
Is there any efficient way of drawing image blurry progressively in didReceiveData? so drawInRect does not take too much time to draw. Or If anyone has custom drawRect method which efficiently displays image progressively as data received in didReceiveData.
I have used NYXImagesKit for something similar, downloading images while not blocking the main thread and showing the image progressively. Ive written a really quick and dirty example to illustrate the basic workings. I load the image in a UITableview to show that it doesn't block the User Interface(Main Thread). You can scroll the tableview while the image is loading. Don't forget to add the correct Frameworks, there are a few. Heres the link to the project on Github:
https://github.com/HubertK/ProgressiveImageDownload
It's really easy to use,create a NYXProgressiveImageView object, set the URL and it will do all the work for you when you call:
loadImageAtURL:
It's a subclass of UIImageView, Works like magic! Here's a link to the developers site:
http://www.cocoaintheshell.com/2012/01/nyximageskit-class-nyxprogressiveimageview/
I suggest pulling the image data in an asynchronous manner and then applying a correction in order to obtain a valid conversion from partially downloaded NSData to an UIImage:
NSURLRequest *theRequest = [NSURLRequest requestWithURL:
[NSURL URLWithString: imageRequestString]
cachePolicy: NSURLRequestReloadIgnoringCacheData
timeoutInterval: 60.0];
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest: theRequest
delegate: self];
if (theConnection)
receivedData = [[NSMutableData data] retain];
.......
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[receivedData appendData: data];
NSInvocationOperation *operation =
[[NSInvocationOperation alloc] initWithTarget: self
selector: #selector(loadPartialImage)
object: nil];
[[[NSOperationQueue alloc] init] autorelease] addOperation: operation];
[operation release];
}
- (void)loadPartialImage {
// This is where you would call the function that would "stitch up" your partial
// data and make it appropriate for use in UIImage's imageWithData
NSData *validPartialData =
[self validImageRepresentationFromPartialImageData: receivedData];
UIImage *partialImage = [UIImage imageWithData: validPartialData];
[imageView performSelectorOnMainThread: #selector(setImage:)
withObject: partialImage
waitUntilDone: NO];
}
+ (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[connection release];
UIImage *fullImage = [UIImage imageWithData: receivedData];
imageView.image = fullImage;
}
Note that I did not provide the code for validImageRepresentationFromPartialImageData, as, at the moment, I have no clear, specific idea, on how to implement such a correction, or if the [UIImage imageWithData:] wouldn't actually accept partial data as input by default. As you can see, the coercion and UIImage creation would happen on a different thread, while the main thread would only display the updates as they come.
If you are receiving too frequent updates and they are still blocking the interface, you could:
a. Make the image requests on a different thread as well.
b. Reduce the frequency of the UIImageView's updates, by only calling setImage once in 10 or 100 updates, according to the zise of your image.
I usually use a really simple GCD pattern for async image loading:
Create a GCD queue in which you load the image data form your web server
Set the image data in your main queue
Example:
dispatch_queue_t image_queue = dispatch_queue_create("com.company.app.imageQueue", NULL);
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_async(image_queue, ^{
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[record imageURLString]];
dispatch_async(main_queue, ^{
[imageView setImage:[UIImage imageWithData:imageData]];
});
});
Probably didReceiveData is called too often! Just use a NSTimerand update the image regularly in 1-2second steps. That should work more efficiently.
Also you can use performSelectorInBackgroundto convert your NSData to an UIImage;
And then call performSelectorOnMainThreadto set the image into the UIImage View. So the converting stuff won't block the main thread.
Have you considered chopping up your images into smaller chunks on the server, then redrawing whenever a complete chunk has been received? This would give you control over the "progressiveness" of the load and the frequency of redraws by changing the chunk size. Not sure this is the kind of progressive load you're after, though.
If you have control of the server, split the image into tiles and also create a low res image. Display the low res version first in the lowest layer, and load the tiles on top drawing them as they load?
You can create a subclass of UIImageView with the URL of the image and a startDownload method.
It's a basic sample it must be improved.
#property (nonatomic, strong) NSURL *imageURL;
- (void)startDownload;
#implementation ImgeViewSubClass
{
NSURLConnection *connection;
NSMutableData *imageData;
}
The start download method:
- (void)startDownload
{
NSURLRequest *request = [NSURLRequest requestWithURL:imageURL];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection start];
imageData = [NSMutableData data];
}
Delegate method from NSURLConnectionDataDelegate
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
#synchronized(imageData)
{
[imageData appendData:data];
}
// this part must be improved using CGImage instead of UIImage because we are not on main thread
UIImage *image = [UIImage imageWithData:imageData];
if (image) {
[self performSelectorOnMainThread:#selector(setImage:) withObject:image waitUntilDone:NO];
}
});
}
The Answer is in ImageIO.framework , its very simple actually
first you create a CGImageSourceRef mySource ,instantiate it using CGImageSourceCreateIncremental() .
setup and start an NSURLConnection with the image Url.
in connection:didReceiveData: , append the received data to your placeholder data , and update the image source by calling
CGImageSourceUpdateData(imageSource, (CFDataRef)imageData, NO);
then load the partially loaded part of the image to your UIImageView
self.image = [UIImage imageWithCGImage:CGImageSourceCreateImageAtIndex(imageSource, 0, nil)];
in connectionDidFinishLoading: finalise by calling
CGImageSourceUpdateData(imageSource, (CFDataRef)imageData, YES);
self.image = [UIImage imageWithCGImage:CGImageSourceCreateImageAtIndex(imageSource, 0, nil)];
CFRelease(imageSource);
imageData = nil;
here is a sample code i wrote :
https://github.com/mohammedDehairy/MDIncrementalImageView
Why don't you use ASIHTTPRequest request:
#import "ASIHTTPRequest.h"
This will help to load/draw in background, can perform other task too.
Try this one:
#import "ASIHTTPRequest.h"
[self performSelectorInBackground:#selector(DownLoadImageInBackground:)
withObject:YOUR IMAGE ARRAY];
-(void) DownLoadImageInBackground:(NSArray *)imgUrlArr1
{
NSURL * url = [Image URL];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
}
-(void)requestFailed:(ASIHTTPRequest *)request
{
NSLog(#"URL Fail : %#",request.url);
NSError *error = [request error];
// you can give here alert too..
}
-(void)requestFinished:(ASIHTTPRequest *)request
{
/////////// Drawing Code Here////////////////////
NSData *responseData = [request responseData];
UIImage *imgInBackground = [[UIImage alloc] initWithData:responseData];
[imageView setImage: imgInBackground];
}
I am not sure how the other parts of your code(reg this module) is implemented but give the following a try,
Try to use this selector with the run loop mode set to NSDefaultRunLoopMode
[self performSelectorOnMainThread:#selector(processImage:)
withObject:objParameters
waitUntillDone:NO
modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]]
This execution will free up your UI interactions, let me know if it helped please.
For more info : APPLE DOCS
//JImage.h
#import <Foundation/Foundation.h>
#interface JImage : UIImageView {
NSURLConnection *connection;
NSMutableData* data;
UIActivityIndicatorView *ai;
}
-(void)initWithImageAtURL:(NSURL*)url;
#property (nonatomic, retain) NSURLConnection *connection;
#property (nonatomic, retain) NSMutableData* data;
#property (nonatomic, retain) UIActivityIndicatorView *ai;
#end
//JImage.m
#import "JImage.h"
#implementation JImage
#synthesize ai,connection, data;
-(void)initWithImageAtURL:(NSURL*)url {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[self setContentMode:UIViewContentModeScaleToFill];
if (!ai){
[self setAi:[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]];
[ai startAnimating];
[ai setFrame:CGRectMake(27.5, 27.5, 20, 20)];
[ai setColor:[UIColor blackColor]];
[self addSubview:ai];
}
NSURLRequest* request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)theConnection didReceiveData:(NSData *)incrementalData {
if (data==nil)
data = [[NSMutableData alloc] initWithCapacity:5000];
[data appendData:incrementalData];
NSNumber *resourceLength = [NSNumber numberWithUnsignedInteger:[data length]];
NSLog(#"resourceData length: %d", [resourceLength intValue]);
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"Connection error...");
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[ai removeFromSuperview];
}
- (void)connectionDidFinishLoading:(NSURLConnection*)theConnection
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[self setImage:[UIImage imageWithData: data]];
[ai removeFromSuperview];
}
#end
//Include the definition in your class where you want to use the image
-(UIImageView*)downloadImage:(NSURL*)url:(CGRect)frame {
JImage *photoImage=[[JImage alloc] init];
photoImage.backgroundColor = [UIColor clearColor];
[photoImage setFrame:frame];
[photoImage setContentMode:UIViewContentModeScaleToFill];
[photoImage initWithImageAtURL:url];
return photoImage;
}
//call the function
UIImageView *imagV=[self downloadImage:url :rect];
//you can call the downloadImage function in looping statement and subview the returned imageview.
//it will help you in lazy loading of images.
//Hope this will help
I'm loading the images from URL in UITableView. But it's very slow when loading an view. Here's an example,
UIImage *image = nil;
image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://calcuttans.com/palki/wp-content/uploads/2009/02/kidscover-small.png"]]];
In Table view, UIButton i'm setting the background image.
Please Can you provide the sample.
FYI : I'm used the LazzyTable sample program but it's not much helpful. Can you suggest any other samples.
Load image asynchronously
NSURL* url = [NSURL URLWithString:#"http://calcuttans.com/palki/wp-content/uploads/2009/02/kidscover-small.png"];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response,
NSData * data,
NSError * error) {
if (!error){
NSImage* image = [[NSImage alloc] initWithData:data];
// do whatever you want with image
}
}];
There are some open source libraries available for this:
HJCache
SDWebImage
These libraries download image in a asynchronous manner and cache it for further use.
Try to implement AFNetworking. It uses async requests to download the image, you are currently blocking your view with every download.
You can then use an AFImageRequestOperation to download your image.
if you load the image all download from the internet every time , it must be very slow.
I think you shuold exist your download image to the filePath , and when you will load the image , you can check whether the image has been downloaded before , if not ,then download. if it has been downloaded , you can use imageWithContentsOfFile: method to load the image
//Make use of dispatch queue for faster processing of data. add this in viewDidLoad
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSData * data=[NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]];
[self performSelectorOnMainThread:#selector(setImage:) withObject:data waitUntilDone:YES];
});
//once data is got set the image and reload tableview
-(void)setImage:(NSData *)responseData
{
image = [UIImage imageWithData:responseData];
[tableView reloadData];
}
maybe you can use asihttprequest to lazy load images. use ASINetworkQueues
You've to use NSOperationQueue to make your tableview efficient.
Check this icodeblog tutorial and raywenderlich tutorial
Have a look at this tutorial. It helped me a lot. When I was using it I was quite new to iOS in general and it was helpful not only with respect to loading images from the web.
http://www.markj.net/iphone-asynchronous-table-image/
With AFNetworking it is more easy.
//AFNetworking
#import "UIImageView+AFNetworking.h"
[cell.iboImageView setImageWithURL:[NSURL URLWithString:server.imagen] placeholderImage:[UIImage imageNamed:#"qhacer_logo.png"]];
I've been researching and haven't found any answer to this question - sendAsynchronousRequest vs. dataWithContentsOfURL.
Which is more efficient? more elegant? safer? etc.
- (void)loadImageForURLString:(NSString *)imageUrl
{
self.image = nil;
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSURLRequest * request = [NSURLRequest requestWithURL:[NSURL URLWithString:imageUrl]];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response, NSData * data, NSError * connectionError)
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
if (data) {
self.image = [UIImage imageWithData:data];
}
}];
}
OR
- (void)loadRemoteImage
{
self.image = nil;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSData * imageData = [NSData dataWithContentsOfURL:self.URL];
if (imageData)
self.image = [UIImage imageWithData:imageData];
dispatch_async(dispatch_get_main_queue(), ^{
if (self.image) {
[self setupImageView];
}
});
});
}
So I've come up with an answer for my own question:
Currently there are 3 main ways to load images async.
NSURLConnection
GCD
NSOperationQueue
Choosing the best way is different for every problem.
For example, in a UITableViewController, I would use the 3rd option (NSOperationQueue) to load an image for every cell and make sure the cell is still visible before assigning the picture. If the cell is not visible anymore that operation should be cancelled, if the VC is popped out of stack then the whole queue should be cancelled.
When using NSURLConnection + GCD we have no option to cancel, therefore this should be used when there is no need for that (for example, loading a constant background image).
Another good advice is to store that image in a cache, even it's no longer displayed, and look it up in cache before launching another loading process.
sendAsynchronousRequest is better, elegant and whatever you call it. But, personally, I prefer creating separate NSURLConnection and listen to its delegate and dataDelegate methods. This way, I can: 1. Set my request timeout. 2. Set image to be cached using NSURLRequest's cache mechanism (it's not reliable, though). 2. Watch download progress. 3. Receive NSURLResponse before actual download begins (for http codes > 400). etc... And, also, it depends on cases like, image size, and some other requirements of your app. Good luck!
I need you to humor me during this question. This is somewhat pseudo code because the actual situation is quite complex. I wouldn't load an image this way unless I needed to. Assume that I need to.
NSURL *bgImageURL = [NSURL URLWithString:#"https://www.google.com/images/srpr/logo3w.png"];
UIImage *img = [UIImage imageWithData:[NSData dataWithContentsOfURL:bgImageURL]];
[self.anIBOutletOfUIImageView setImage:img];
but I crash out with
-[__NSCFData _isResizable]: unrecognized selector sent to instance 0x9508c70
How can I load an image from a URL into NSData and then load that NSData into a UIImage and set that UIImage as the image for my UIImageView?
Again, I realize this sounds like nonsense, but due to an image caching system I'm using I have to do things this way :(
How I usually handle this situation (not compiled, not tested):
NSURL * url = [NSURL URLWithString:#"https://www.google.com/images/srpr/logo3w.png"];
NSURLRequest * request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue currentQueue]
completionHandler:^(NSURLResponse * resp, NSData * data, NSError * error) {
// No error handling - check `error` if you want to
UIImage * img = [UIImage imageWithData:data];
[self.imageView performSelectorOnMainThread:#selector(setImage:) withObject:img waitUntilDone:YES];
}];
This avoids the long-running network request implied by the call to dataWithContentsOfURL:, so your app can continue doing things on the main thread while the data downloads for the image.
As a side note, you seem to be running into the same error as this question; you might want to check that you're not running into object allocation problems.
I plugged this code into a new, iPad "Single view application" template:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSURL *bgImageURL = [NSURL URLWithString:#"https://www.google.com/images/srpr/logo3w.png"];
NSData *bgImageData = [NSData dataWithContentsOfURL:bgImageURL];
UIImage *img = [UIImage imageWithData:bgImageData];
[[self Imageview] setImage:img];
}
I got the image to load up properly. I even tried different content modes for the UIImageView, and they all worked as expected.
If you start from scratch, do you get the same problem?
The error message indicates that you're sending a message of "_isResizable" to an NSData object. Maybe you're inadvertently setting the UIImageView's image property equal to the NSData instead of the UIImage.
Edit/Update
I even went back and tried different versions of "one-lining" the code to see if any of that mattered, since my "working" copy was slightly altered from your non-working copy. I couldn't get the code to break. My guess would be that the code in question isn't broken, but something nearby is...