stop NSData loading from server - ios

I have the following method, which basically loads an array of image data into an array:
-(void)loadImages:(NSMutableArray*)imagesURLS{
//_indexOfLastImageLoaded = 0;
[_loadedImages removeAllObjects];
_loadedImages = [[NSMutableArray alloc]init];;
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
for (int i=0; i<imagesURLS.count;i++){
NSLog(#"loading image for main image holder at index %i",i);
NSData *imgData = [NSData dataWithContentsOfURL:[imagesURLS objectAtIndex:i]];
UIImage *img = [UIImage imageWithData:imgData];
[_loadedImages addObject:img];
//_indexOfLastImageLoaded++;
}
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"_loadedImages download COMPLETE");
});
});
}
I want to be able to stop it when, for example, a user moves away from the view controller that these images are being loaded in. What is the best way to do so?
Thanks!

You can't cancel NSData dataWithContentsOfUrl:. The best way to achieve cancelable, asynchronous downloading is by using NSURLConnection and the NSURLConnectionDataDelegate.
You set up an NSMutableData object to accumulate all the data as it comes in in chunks. Then when all the data has arrived, you create your image and use it.
.h
#interface ImageDownloader : NSObject <NSURLConnectionDataDelegate>
#property (strong, nonatomic) NSURLConnection *theConnection;
#property (strong, nonatomic) NSMutableData *buffer;
#end
.m
-(void)startDownload
{
NSURL *imageURL = [NSURL URLWithString: #"http://example.com/largeImage.jpg"];
NSURLRequest *theRequest = [NSURLRequest requestWithURL: imageURL];
_theConnection = [[NSURLConnection alloc] initWithRequest: theRequest delegate: self startImmediately: YES];
}
-(void)cancelDownload
{
// CANCELS DOWNLOAD
// THROW AWAY DATA
[self.theConnection cancel];
self.buffer = nil;
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// INITIALIZE THE DOWNLOAD BUFFER
_buffer = [NSMutableData data];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// APPEND DATA TO BUFFER
[self.buffer appendData: data];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// DONE DOWNLOADING
// CREATE IMAGE WITH DATA
UIImage *theImage = [UIImage imageWithData: self.buffer];
}

If you want to be more flexible with cancel request i advice you to use NSOperationQueue instead of pushing all request in a row.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:1];
for (int i=0; i<allImagesCount; i++) {
[queue addOperationWithBlock:^{
// load image
}];
}
// for canceling operations
[queue cancelAllOperations];
In you current code you can also define static field and check in for loop, but the best way will be using SDWebImage - https://github.com/rs/SDWebImage for loading image asynch.

Related

How to get API Data Asynchronously in Objective-C?

All my work is going fine, but There is a little problem in it. I have my NSURLRequest in -(void)viewDidLoad{} and it took some time to fetch data from server. I want it to be done in asynchronous way.
Following is my code please suggest me what should I implement.?
Thanks in advance to all of you. :)
- (void)viewDidLoad {
[super viewDidLoad];
[[self tableView2]setDelegate:self ];
[[self tableView2]setDataSource:self];
array=[[NSMutableArray alloc]init];
NSString *castString = [NSString stringWithFormat:#"https://api.themoviedb.org/3/movie/%#/credits?api_key=c4bd81709e87b12e6c74a08609433c49",movieIDinString];
NSURL *url=[NSURL URLWithString:castString];
NSURLRequest *request=[NSURLRequest requestWithURL:url];
connection=[NSURLConnection connectionWithRequest:request delegate:self];
if (connection)
{
webData= [[NSMutableData alloc]init];
}
try this..
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
//Background Thread
NSString *castString = [NSString stringWithFormat:#"https://api.themoviedb.org/3/movie/%#/credits?api_key=c4bd81709e87b12e6c74a08609433c49",movieIDinString];
NSURL *url=[NSURL URLWithString:castString];
NSURLRequest *request=[NSURLRequest requestWithURL:url];
connection=[NSURLConnection connectionWithRequest:request delegate:self];
if (connection)
{
webData= [[NSMutableData alloc]init];
}
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run UI Updates
// reload table view here
[_activityIndicatorImageView stopAnimating];
});
});
If you are using API, it will take some time, to fetch data from server. At this time, you have to use background thread and show activity indicator in main thread. After getting data from API, you need to change thread to main thread. Please check my below code.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// background thread
// code for API call
dispatch_async(dispatch_get_main_queue(), ^{
// main thread
});
});
You can use callback method also.
[helperApi instaUserDetails:finderDetailsDataDict andCallback:^(id jsonResponse) {
dispatch_async(dispatch_get_main_queue(), ^{
if ([[jsonResponse objectForKey:#"code"] intValue] == 200) {
userDetailsDict = [jsonResponse objectForKey:#"data"];
mediaArray = [[[[jsonResponse objectForKey:#"data"] objectForKey:#"user"] objectForKey:#"media"] objectForKey:#"nodes"];
}
[activityIndicator stopAnimating];
[self createUI];
});
}];
NSURLConnection is deprecated now. Try to use NSURLSession.
Try AFNeworking. It provides several options for async downloads/uploads, with completions blocks.

Asynchronously download images for UITableView using NSURLConnection

I have a TableView with customCells, when user press Start button on some cell the loading starts. There are many such cells, so I need to implement this downloading in parallel (asynchronously).
For image downloading and updating the cell in Table view I use next code:
#define myAsyncQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
I include this method into the async queue, that I supposed should enable parallel downloading of images.
- (void)didClickStartAtIndex:(NSInteger)cellIndex withData:
(CustomTableViewCell*)data
{
dispatch_async(myAsyncQueue, ^{
self.customCell = data;
self.selectedCell = cellIndex;
ObjectForTableCell* tmp =[self.dataDictionary objectForKey:self.names[cellIndex]];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:tmp.imeageURL]
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:60.0];
self.connectionManager = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
});
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
self.urlResponse = response;
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
NSDictionary *dict = httpResponse.allHeaderFields;
NSString *lengthString = [dict valueForKey:#"Content-Length"];
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
NSNumber *length = [formatter numberFromString:lengthString];
self.totalBytes = length.unsignedIntegerValue;
self.imageData = [[NSMutableData alloc] initWithCapacity:self.totalBytes];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.imageData appendData:data];
self.customCell.progressView.progress = ((100.0/self.urlResponse.expectedContentLength)*self.imageData.length)/100;
float per = ((100.0/self.urlResponse.expectedContentLength)*self.imageData.length);
self.customCell.realProgressStatus.text = [NSString stringWithFormat:#"%0.f%%", per];
}
I tried to set this block to queue - main queue - cause its the place where image is already downloaded,
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
dispatch_async(dispatch_get_main_queue(), ^{
self.customCell.realProgressStatus.text = #"Downloaded";
UIImage *img = [UIImage imageWithData:self.imageData];
self.customCell.image.image = img;
self.customCell.tag = self.selectedCell;
});
[self.savedImages setObject:img forKey:self.customCell.nameOfImage.text];
NSNumber *myNum = [NSNumber numberWithInteger:self.selectedCell];
[self.tagsOfCells addObject:myNum];
}
Without all queues(when I comment it)all works properly - but just 1 downloading at a ones.
But when I tried to implement code with queues as a result it doesn't download anything. I understand that I did smh wrong but I can't define it.
Thanks a lot for any help in advance.
If your looking out for starting it form basics I guess you should start with NSURLSession as NSURLConnection most of implementation had been deprecated and won't be available after iOS 9. For complete reference URL Session Programming Guide and tutorial
Coming back to your question you should do something similar to this took it from tutorial
// 1
NSURLSessionDownloadTask *getImageTask =
[session downloadTaskWithURL:[NSURL URLWithString:imageUrl]
completionHandler:^(NSURL *location,
NSURLResponse *response,
NSError *error) {
// 2
UIImage *downloadedImage =
[UIImage imageWithData:
[NSData dataWithContentsOfURL:location]];
//3
dispatch_async(dispatch_get_main_queue(), ^{
// do stuff with image
_imageWithBlock.image = downloadedImage;
});
}];
// 4
[getImageTask resume];
But my personal recommendation is go for AFNetworking which is best for iOS networking and widely used/tested in iOS app world.
For image download using AFNetworking
[_imageView setImageWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://i.imgur.com/fVhhR.png"]]
placeholderImage:nil
success:^(NSURLRequest *request , NSHTTPURLResponse *response , UIImage *image ){
NSLog(#"Loaded successfully: %d", [response statusCode]);
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error){
NSLog(#"failed loading: %#", error);
}
];
EDIT : Async downloading using concurrency
// get main dispact queue
dispatch_queue_t queue = dispatch_get_main_queue();
// adding downloading task in queue using block
dispatch_async(queue, ^{
NSData* imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]];
UIImage* image = [[UIImage alloc] initWithData:imageData];
// after download compeletes geting main queue again as there can a possible crash if we assign directly
dispatch_async(dispatch_get_main_queue(), ^{
_imageWithBlock.image = image;
});
});
Use this sample code from Apple to solve your problem of lazy loading.

How can I get my asynchronous url images loaded into NSMutableArray in order?

I am trying to load images by their URL and store them in NSMutableArray in order. My current code works properly if I were not to care about storing the images in order, however it stores them not in order. It currently stores the images in the articleImage array based on the speed at which the asynchronous requests are completed. I have tried playing around with insertObject:AtIndex but could not get anything to work. To clarify, the NSMutableArray that I am trying to store the images in (in orderly fashion) is articleImage.
Here is some code from my viewDidLoad:
dispatch_async(dispatch_get_main_queue(), ^{
if(articleInfoJSONArray.count > 0)
{
for(int i=0; i<articleInfoJSONArray.count; i++)
{
[issueID addObject:[[articleInfoJSONArray objectAtIndex:i] objectForKey:#"issueID"]];
[articleID addObject:[[articleInfoJSONArray objectAtIndex:i] objectForKey:#"articleID"]];
NSString *imageLink = [[articleInfoJSONArray objectAtIndex:i] objectForKey:#"articleImage"];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageLink]];
UIImage *image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
[articleImage addObject:image];
if(articleImage.count == articleInfoJSONArray.count)
[self imagesLoaded];
});
});
}
}
});
Here is my imagesLoaded:
- (void)imagesLoaded
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
ViewController * vc = [storyboard instantiateViewControllerWithIdentifier:#"MainView"];
[self presentViewController:vc animated:NO completion:nil];
}
Try to use dispatch_group. A dispatch group monitors work that has been added to it, and it will know when that work is done. :) http://commandshift.co.uk/blog/2014/03/19/using-dispatch-groups-to-wait-for-multiple-web-services/
One way i did an image download is with NSOperationQueue and NSOperation. You could define a NSOperationQueue in your header file:
#property (strong, nonatomic) NSOperationQueue *sequentialOperationQueue;
in your implementation do:
self.sequentialOperationQueue = [[NSOperationQueue alloc] init];
self.sequentialOperationQueue.maxConcurrentOperationCount = 1;
then you can add:
for (NSDictionary *imageDict in imagesToFetch) {
ImageDownloadOperation *imgDownloadOperation = [[ImageDownloadOperation alloc] initWithImageLocationDict:imageDict];
[self.sequentialOperationQueue addOperation:imgDownloadOperation];
}
LogoDownloadOperation is a subclass of NSOperation. this way you always have only one active download and process them in the order you want. For details on NSOperation check the apple doc.
in extract i did in ImageDownloadOperation:
- (void)start {
NSURL *imageUrl = [NSURL URLWithString:self.imageDict[#"imageUrl"]];
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
NSURLSessionDownloadTask *downloadPhotoTask = [session
downloadTaskWithURL:imageUrl
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
if (error) {
self.sessionTask = nil;
[self done];
return;
}
NSData *imageData = [NSData dataWithContentsOfURL:location];
NSBlockOperation *saveImageBlockOperation = [NSBlockOperation blockOperationWithBlock:^{
[SharedAppDelegate.entityManager saveImage:imageData
imageDict:self.imageDict
inManagedObjectContext:SharedAppDelegate.managedObjectContext];
}];
saveImageBlockOperation.qualityOfService = NSQualityOfServiceBackground;
[[NSOperationQueue mainQueue] addOperation:saveImageBlockOperation];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
self.sessionTask = nil;
[self done];
}];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}
As you can see, i store the imageData via my AppDelegate in CoreData. Instead of my way, you could give the ImageDownloadOperation a pointer to your NSMutableArray, then you can store the data direct in your array.
You could make an array of [UIImage new] then once the task is complete
replace the empty image images[i] = newImage
EDIT
NSMutableArray *imageArray = [NSMutableArray new];
for (int i=0; i<articleInfoJSONArray.count; i++) {
[imageArray addObject:[UIImage new]];
}
for (int i=0; i<articleInfoJSONArray.count; i++) {
dispatch_async(dispatch_get_main_queue(), ^{
//download image
imageArray[i] = downloadedImage;
});
}

setImageWithUrl is not showing the image at firstime

My app displays a lot of images, so I am using page control to display the images in single controller.
User can able to see all the images by swiping.
I am using AFNetworking Library to show the good performance. I have used the property setImageWithUrl
// This is myCode
NSURL *myUrl = [NSURL URLWithString:#"http://mdb.scicloudsolutions.com:8001/sites/default/files/landscape_design_1.jpg"];
[imageView setImageWithUrl:myUrl];
it's not showing at first time only..
When I Print url and image
NSLog(#"Url is .. : %# image. is : %#",myUrl,imageView.image);
Response:
am receiving the url value but not image value.
It shows image is null at first time.
How to solve?
Here You Set [imageView setImageWithUrl:myUrl]; So it take time to load an Image in to ImageView.
Use SDWebImage Library For Better. Just Simple Download this Library From Link and Set Image like as
[imageView sd_setImageWithURL:url
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
Use following solution for this:
#property (nonatomic, strong) NSMutableData *data
#property (nonatomic, strong) NSURLConnection *connection;
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
Add these delegate methods to your view controller:
- (void)connection:(NSURLConnection *)connection
didReceiveData:(NSData *)incrementalData {
if (data==nil) {
data = [[NSMutableData alloc] initWithCapacity:2048];
}
[data appendData:incrementalData];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection {
connection = nil;
UIImage *image = [UIImage imageWithData:data];
// Set above image to your imageView.
data = nil;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
/*============= YOU ARE ON BACK GROUND THREAD ==================*/
UIImageView *yourImageView=[[UIImageView alloc] init];
UIImage *img=[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"pass your url here"]]];
dispatch_async(dispatch_get_main_queue(), ^(void) {
/*============= YOU ARE ON MAIN THREAD ==================*/
yourImageView.image=img;
});
});

Is there an event for when a UIImage has finished loading

I am displaying a UIImage view with this code:
NSURL *imageUrl = [[NSURL alloc]initWithString:#"http://..."];
NSData *imageData = [[NSData alloc] initWithContentsOfURL:imageUrl];
UIImage *audioArt =[[UIImage alloc] initWithData:imageData];
UIImageView *artView =[[UIImageView alloc] initWithImage:audioArt];
artView.contentMode = UIViewContentModeScaleAspectFit;
artView.autoresizesSubviews = NO;
artView.frame = viewRef.bounds;
[artView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
[artView setBackgroundColor:[UIColor blackColor]];
[viewRef setBackgroundColor:[UIColor blackColor]];
[viewRef addSubview:artView];
Is there an event in Objective C to tell when this UIImage has finished loading or when the UIImageView has finished displaying the image?
Many thanks.
in your .h
#interface YourClass : YourSuperclass<NSURLConnectionDataDelegate>
#property (nonatomic) NSMutableData *imageData;
#property (nonatomic) NSUInteger totalBytes;
#property (nonatomic) NSUInteger receivedBytes;
And somewhere call
NSURL *imageUrl = [[NSURL alloc]initWithString:#"http://..."];
NSURLRequest *request = [NSURLRequest requestWithURL: imageUrl];
NSURLConnection *connection = [NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
And also implement the delegate methods
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) urlResponse;
NSDictionary *dict = httpResponse.allHeaderFields;
NSString *lengthString = [dict valueForKey:#"Content-Length"];
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
NSNumber *length = [formatter numberFromString:lengthString];
self.totalBytes = length.unsignedIntegerValue;
[self.imageData = [[NSMutableData alloc] initWithLength:self.totalBytes];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.imageData appendData:data];
self.receivedBytes += data.length;
// Actual progress is self.receivedBytes / self.totalBytes
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
imageView.image = [UIImage imageWithData:self.imageData];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
//handle error
}
Update:
Since NSMutableData initWithLength: creates a data object whose raw data is filled with zeros, replace initWithLength: with just init and it will works.
UIImage doesn't have any delegate or callback which tells you about its successful loading.
If you are getting the image from any url, you can use NSURL's delegates and notifications to track if you received the image or not.
Also you can achieve it as :
- (void)loadImage:(NSString *)filePath {
[self performSelectorInBackground:#selector(loadImageInBackground:) withObject:filePath];
}
- (void)loadImageInBackground:(NSString *)filePath {
#autoreleasepool{
UIImage *image = [[UIImage alloc] initWithContentsOfFile:filePath];
[self performSelectorOnMainThread:#selector(didLoadImageInBackground:) withObject:image waitUntilDone:YES];
}
}
- (void)didLoadImageInBackground:(UIImage *)image {
self.imageView.image = image;
}

Resources