How make update of my UILabel in xcode - iphone

i have my code for parsing:
NSError* error = nil;
NSString* text = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://site.ch/parse.php"]
encoding:NSASCIIStringEncoding
error:&error];
From this code i getting name track from url radio.
Also i getting this to my label:
[labelName setStringValue:text];
Question: How to update my label? I want that my label to be updated after 5 sec from URL. I used timer schedule..but after this my app became very slow...help please.

Assuming that you just put the above code in viewDidLoad of a view controller or similar, you actually block the main thread (i.e, the UI thread), causing the app to be in-responsive for the amount of time it takes to retrieve the string from site.ch/parse.php. As suggested, you should download the string in the background. Also, modifying UI must be done on the main thread:
dispatch_queue_t queue = dispatch_get_global_queue(0,0);
dispatch_async(queue, ^{
NSError* error = nil;
NSString* text = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://site.ch/parse.php"]
encoding:NSASCIIStringEncoding
error:&error];
// checking if error == nil would be appropriate
dispatch_async(dispatch_get_main_queue(), ^{
self.labelName.text = text;
});
});
This sample (my apologies for any typos) downloads the text from site.ch/parse.php in the background and sets the label when finished. Also, as suggested here it would be a good idea to display a placeholder text while download the real text.
Also, have a look at performSelector:withObject:afterDelay if you wish to do so.

you should try to use background thread or GCD thread to update your text continuously.

Why dont you use any background thread to get data from the url and after getting you data you can update the value of the label.By this there should be no effect on your app performance.

Related

ASIHTTPRequest working on emulator but not iphone

Hey guys I have done lots of work with ASIHTTPRequest so far and use it several times in my ios application, however in one area of my app where I have added another asihttprequest method its not working properly.
First of all it must be said that the reason I'm trying to spread the load of downloading and parsing the data.. If I do this on the subview it takes a good 2-3 second to go through the second xml sheet and get all of the related values out.. where as If I do it on this mainview where people are not seeing anything load etc then when the go back to the subview it should look almost instant. I don't know how correct this is but I figure its a okay thing to do to make the app feel abit snappier.
So I am setting it the asihttprequest methods identically as the other ones that work minus caching.
What happens is I select a table cell from the main view that loads the second view and parses a bunch of info to the tableview. the User then selects a value which is passed back to the main view and displayed.
I then parse another lot of xml data checking the selected valueID against everything in the second xml sheet. so that when the user selects the second cell I pass all the data that was just parsed over to the second view to make it look as though its loaded alot faster.
Heres a flow chart of that will explain what I'm trying to do abit better
This is what the parser code looks like in the main view which is the one thats working in the emulator but not on the iphone.
This is my protocol that I call from the subview and pass all the values I need to over and fire off the request to the mainviews ASIHTTPRequest.
- (void) setManufactureSearchFields:(NSArray *)arrayValues withIndexPath:(NSIndexPath *)myIndexPath
{
manufactureSearchObjectString = [[arrayValues valueForKey:#"MANUFACTURER"] objectAtIndex:0];
manufactureIdString = [[arrayValues valueForKey:#"MANUID"] objectAtIndex:0]; //Restricts Models dataset
manufactureResultIndexPath = myIndexPath;
[self.tableView reloadData]; //reloads the tabels so you can see the value in the tableViewCell.
//need some sort of if statment here so that if the back button is pressed modelSearchObjectString is not changed..
if (oldManufactureSearchObjectString != manufactureSearchObjectString) {
modelResultIndexPath = NULL;
modelSearchObjectString = #"empty";
oldManufactureSearchObjectString = [[NSString alloc] initWithFormat:manufactureSearchObjectString];
}
//These two lines below are what execute ASIHTTPRequest and set up my parser etc
dataSetToParse = #"ICMod"; // This sets the if statment inside parserDidEndDocument
[self setRequestString:#"ICMod.xml"]; //Sets the urlstring for XML inside setRequestString
}
This then fires the ASIHTTPRequest delegate methods.
- (IBAction)setRequestString:(NSString *)string
{
//Set database address
//NSMutableString *databaseURL = [[NSMutableString alloc] initWithString:#"http://127.0.0.1:8888/codeData/"]; // imac development
NSMutableString *databaseURL = [[NSMutableString alloc] initWithString:#"http://127.0.0.1:8888/codeData/"]; // iphone development
//PHP file name is being set from the parent view
[databaseURL appendString:string];
NSLog(#"%#", databaseURL);
//call ASIHTTP delegates (Used to connect to database)
NSURL *url = [NSURL URLWithString:databaseURL];
//This sets up all other request
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
}
When I run this through debug with break points while testing on the iphone this is where the app falls over.. but On the emulator it has no problems.
This next method never gets called when testing on the iphone but workds sweet on the emulator.
- (void)requestFinished:(ASIHTTPRequest *)request
{
responseString = [request responseString]; //Pass requested text from server over to NSString
capturedResponseData = [responseString dataUsingEncoding:NSUTF8StringEncoding];
[self startTheParsingProcess:capturedResponseData];
}
This is the only other delegate that is fired when testing on the iphone, sends me an alret saying the connection has timed out.
- (void)requestFailed:(ASIHTTPRequest *)request
{
NSError *error = [request error];
NSLog(#"%#", error);
UIAlertView *errorAlert = [[UIAlertView alloc] initWithTitle:#"Error!" message:#"A connection failure occurred." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[errorAlert show];
}
I don't think you need to see all the parser delegates as I don't think they are the issue as this is where the app falls over...
here is what gets printed to the log...
2011-11-29 14:38:08.348 code[1641:707] http://000.000.000.000:0000/codeData/second.xml
2011-11-29 14:38:18.470 code[1641:707] Error Domain=ASIHTTPRequestErrorDomain Code=2 "The request timed out" UserInfo=0x1e83a0 {NSLocalizedDescription=The request timed out}
If you need more of my code let me know.. but I'm at abit of a loss here as like I say there is no difference to how Im doing this ASIHTTPRequest to other views other than I'm initializing it from the protocol that I'm setting up from the second view.. maybe I should set the values before I reload the table or something... I'm not sure though hopefully someone can help me out with this one and spot the issue I cannot see.
Can you view 'http://127.0.0.1:8888/codeData/' with Safari on the iPhone? Chances are that server isn't available from whatever networks the iPhone is connected to.
If your iMac is using DCHP it is possible that the address has changed since you originally set the value.
Are you sure you have the case correct on the URL? The simulator is much more forgiving on case-sensitivity than the actual device.
Do not fly blind but use an HTTP Proxy like Charles for making sure your requests are actually fired and result into what you expect.

update ui while running in a loop

I have a read-only text field that I use as a log display. I have a operation that removes all the files in app's document directory. I want to insert a line of log when I remove each file. But the text field is only updated when the whole operation got finished. How do I fix this?
Here is my code:
NSFileManager *fm = [[[NSFileManager alloc] init] autorelease];
NSError *error = nil;
for (NSString *fileName in array) {
NSString *filePath = [DOCUMENT_PATH_VALUE stringByAppendingFormat:#"/%#", fileName];
[fm removeItemAtPath:filePath error:&error];
if (!error) {
NSString *log = [NSString stringWithFormat:#"removed success: %#", fileName];
[self logThis:log];
}else{
NSString *log = [NSString stringWithFormat:#"remove failed: %#, %#", fileName, [error localizedDescription] ];
[self logThis:log];
-(void)logThis:(NSString*) text{
NSRange range = NSMakeRange([updateLogTextView.text length], [text length]);
updateLogTextView.text = [updateLogTextView.text stringByAppendingFormat:#"%#\n", text];
[updateLogTextView scrollRangeToVisible:range];
}
You need to move your long-running operation into a background thread/queue and make sure that your UI-updating code is always executed on the main thread.
Example:
- (void)processFiles:(NSArray *)array
{
//You need to create your own autorelease pool in background threads:
NSAutoreleasePool *pool = [NSAutoreleasePool new];
//...
NSString *log = ...;
[self performSelectorOnMainThread:#selector(logThis:) withObject:log waitUntilDone:NO];
//...
[pool release];
}
You would then start your operation using:
[self performSelectorInBackground:#selector(processFiles:) withObject:files];
Using Grand Central Dispatch with blocks would be another option.
Refer to the Threading Programming Guide and the Concurrency Programming Guide for more in-depth information.
You need to look into Threading and making that call Asynchronous. You are currently blocking your main thread (assuming it's called from there). You need to separate the two so a separate thread does the heavy operation while your main thread is updated with the new text in the UI.
I've just started ios dev but sounds like you need to redraw that UIText I'm not sure how though I'd assume that when your log writes that you could dealloc your original UIText object the realloc it and it should redraw with the new log message. Only problem is that it will most likely update very very fast so redrawing wouldnt be worth it unless ios has a sleep function?
Something like:
Alloc and init text
Then in for loop after each write or new message in log dealloc your text and realloc and init your text
This should redraw the UIText object and update the text, if it doesnt expect a memory write error/app crash

<Invalid CFStringRef> when reading metadata from image on iPhone

I read the image from the photo library and I get the metadata using the assets library. I then try to read the user comment exif tag and display it in my text view. Code is here:
[assetLibrary assetForURL:assestURL resultBlock:^(ALAsset *asset) {
ALAssetRepresentation *representation = [asset defaultRepresentation];
NSMutableDictionary *metadataDictPhoto = (NSMutableDictionary*)[representation metadata];
NSLog(#"This is the read metadata I believe: %#",[metadataDictPhoto description]);
metadataDictPhoto = metadataGlobal;
} failureBlock:^(NSError *error) {
NSLog(#"%#",[error description]);
}];
NSMutableDictionary *exifDictionary = (NSMutableDictionary*)[metadataGlobal objectForKey:(NSString*) kCGImagePropertyExifDictionary];
NSString *comment = (NSString*)[exifDictionary valueForKey:(NSString*)kCGImagePropertyExifUserComment];
textView.text = comment;
When I run it, there is no crashes but nothing is displayed in the textview. I have verified using NSLogs that the metadata received from my code is correct, as in I can see my custom exif user comment tag. If I place my mouse over comment it gives me the error . I can't get rid of this.
How can I read the string from the metadata dictionary and get it to display in the textView?
EDIT: DeePak Noticed that I mixed up an assignment statement and I changed it, but that did not fix the issue. While I was looking into this though I found that my NSLogs show that the code is that reads the metadata is passed over and then it runs the dictionary and string code which at this point the metadata isn't create. It then completes the imagepicker delegate function and then it eventually goes to the complete block and then runs the code and ouputs the dictionary and then I can see that everything is correct.
How could I get the code to run immediantly or have the program wait ntil the assetforurl completes?
You have this assignment flipped.
metadataDictPhoto = metadataGlobal;
You need to change this to
metadataGlobal = metadataDictPhoto;
metadataGlobal is probably an instance variable which is why it is not crashing as it continues to be nil here –
NSMutableDictionary *exifDictionary = (NSMutableDictionary*)[metadataGlobal objectForKey:(NSString*) kCGImagePropertyExifDictionary];
I fixed it by adding in the code outside of the completion block to inside the completion block and made sure all the variables were saved to be used then. This worked perfectly.
Now only if I could figure out why it is not writing IPTC correctly I would be set.

blocks in uitableview didSelectRowAtIndexPath and passing autoreleased vars around causing nil behavior

I'm attempting to download an MP3 file from my server when a user selects a row using blocks and a dispatch_queue. Things seem to work great about 80% of the time.
Here is my thought process:
When the user selects the row, update the UI to show that a download has begun (spinner)
Add a block to a background thread to begin the download in the background (I use the thread blocking dataWithContentsOfURL method. I believe this is ok because it is on an async dispatch queue.
on the main_queue, When the download is completed, create an AVAudioPlayer with the data from the dispatch queue.
on the main_queue, Play sound file.
on the main_queue, update UI to reflect that the sound is playing and the download is complete.
Like I said, this works perfectly 80% of the time. When something works 'most' of the time, it screams memory management issues.
So here is the snippet:
//download url
dispatch_queue_t downloadQueue = dispatch_queue_create("com.mycompany.audiodownload", NULL);
dispatch_retain(downloadQueue);
JAAudioMessageEvent *event = [self.cheers objectAtIndex:indexPath.row];
dispatch_async(downloadQueue, ^{
NSURL *sampleURL = [NSURL URLWithString:event.sample];
NSLog(#"start downloading file %#", sampleURL);
NSData *data = [NSData dataWithContentsOfURL:sampleURL];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"play file %#", (data == nil) ? #"data file is nil":#"");
NSError *error;
self.eventPlayer = [[AVAudioPlayer alloc] initWithData:data error:&error];
if (error) {
NSLog(#"there was an error when playing %#", error);
}
[self.eventPlayer play];
self.eventPlayer.delegate = self;
[activityIndicator removeFromSuperview];
[activityIndicator release];
self.eventInProgress = NO;
});
dispatch_release(downloadQueue);
});
So the issue is that during this 20% of the time when things go wrong, it would seem as though the event is being released and I'm given a null url. I fetch a null url and I get an error when attempting to play said null url.
So the question becomes, how do I manage the event object so that it persists over the 2 async threads?
Thanks in advance

iOS download and save image inside app

Is it possible for me to download an image from website and save it permanently inside my app? I really have no idea, but it would make a nice feature for my app.
Although it is true that the other answers here will work, they really aren't solutions that should ever be used in production code. (at least not without modification)
Problems
The problem with these answers is that if they are implemented as is and are not called from a background thread, they will block the main thread while downloading and saving the image. This is bad.
If the main thread is blocked, UI updates won't happen until the downloading/saving of the image is complete. As an example of what this means, say you add a UIActivityIndicatorView to your app to show the user that the download is still in progress (I will be using this as an example throughout this answer) with the following rough control flow:
Object responsible for starting the download is loaded.
Tell the activity indicator to start animating.
Start the synchronous download process using +[NSData dataWithContentsOfURL:]
Save the data (image) that was just downloaded.
Tell the activity indicator to stop animating.
Now, this might seem like reasonable control flow, but it is disguising a critical problem.
When you call the activity indicator's startAnimating method on the main (UI) thread, the UI updates for this event won't actually happen until the next time the main run loop updates, and this is where the first major problem is.
Before this update has a chance to happen, the download is triggered, and since this is a synchronous operation, it blocks the main thread until it has finished download (saving has the same problem). This will actually prevent the activity indicator from starting its animation. After that you call the activity indicator's stopAnimating method and expect all to be good, but it isn't.
At this point, you'll probably find yourself wondering the following.
Why doesn't my activity indicator ever show up?
Well, think about it like this. You tell the indicator to start but it doesn't get a chance before the download starts. After the download completes, you tell the indicator to stop animating. Since the main thread was blocked through the whole operation, the behavior you actually see is more along the lines telling the indicator to start and then immediately telling it to stop, even though there was a (possibly) large download task in between.
Now, in the best case scenario, all this does is cause a poor user experience (still really bad). Even if you think this isn't a big deal because you're only downloading a small image and the download happens almost instantaneously, that won't always be the case. Some of your users may have slow internet connections, or something may be wrong server side keeping the download from starting immediately/at all.
In both of these cases, the app won't be able to process UI updates, or even touch events while your download task sits around twiddling its thumbs waiting for the download to complete or for the server to respond to its request.
What this means is that synchronously downloading from the main thread prevents you from possibly implementing anything to indicate to the user that a download is currently in progress. And since touch events are processed on the main thread as well, this throws out the possibility of adding any kind of cancel button as well.
Then in the worst case scenario, you'll start receiving crash reports stating the following.
Exception Type: 00000020 Exception Codes: 0x8badf00d
These are easy to identify by the exception code 0x8badf00d, which can be read as "ate bad food". This exception is thrown by the watch dog timer, whose job is to watch for long running tasks that block the main thread, and to kill the offending app if this goes on for too long. Arguably, this is still a poor user experience issue, but if this starts to occur, the app has crossed the line between bad user experience, and terrible user experience.
Here's some more info on what can cause this to happen from Apple's Technical Q&A about synchronous networking (shortened for brevity).
The most common cause for watchdog timeout crashes in a network application is synchronous networking on the main thread. There are four contributing factors here:
synchronous networking — This is where you make a network request and block waiting for the response.
main thread — Synchronous networking is less than ideal in general, but it causes specific problems if you do it on the main thread. Remember that the main thread is responsible for running the user interface. If you block the main thread for any significant amount of time, the user interface becomes unacceptably unresponsive.
long timeouts — If the network just goes away (for example, the user is on a train which goes into a tunnel), any pending network request won't fail until some timeout has expired....
...
watchdog — In order to keep the user interface responsive, iOS includes a watchdog mechanism. If your application fails to respond to certain user interface events (launch, suspend, resume, terminate) in time, the watchdog will kill your application and generate a watchdog timeout crash report. The amount of time the watchdog gives you is not formally documented, but it's always less than a network timeout.
One tricky aspect of this problem is that it's highly dependent on the network environment. If you always test your application in your office, where network connectivity is good, you'll never see this type of crash. However, once you start deploying your application to end users—who will run it in all sorts of network environments—crashes like this will become common.
Now at this point, I'll stop rambling about why the provided answers might be problematic and will start offering up some alternative solutions. Keep in mind that I've used the URL of a small image in these examples and you'll notice a larger difference when using a higher resolution image.
Solutions
I'll start by showing a safe version of the other answers, with the addition of how to handle UI updates. This will be the first of several examples, all of which will assume that the class in which they are implemented has valid properties for a UIImageView, a UIActivityIndicatorView, as well as the documentsDirectoryURL method to access the documents directory. In production code, you may want to implement your own method to access the documents directory as a category on NSURL for better code reusability, but for these examples, this will be fine.
- (NSURL *)documentsDirectoryURL
{
NSError *error = nil;
NSURL *url = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
create:NO
error:&error];
if (error) {
// Figure out what went wrong and handle the error.
}
return url;
}
These examples will also assume that the thread that they start off on is the main thread. This will likely be the default behavior unless you start your download task from somewhere like the callback block of some other asynchronous task. If you start your download in a typical place, like a lifecycle method of a view controller (i.e. viewDidLoad, viewWillAppear:, etc.) this will produce the expected behavior.
This first example will use the +[NSData dataWithContentsOfURL:] method, but with some key differences. For one, you'll notice that in this example, the very first call we make is to tell the activity indicator to start animating, then there is an immediate difference between this and the synchronous examples. Immediately, we use dispatch_async(), passing in the global concurrent queue to move execution to the background thread.
At this point, you've already greatly improved your download task. Since everything within the dispatch_async() block will now happen off the main thread, your interface will no longer lock up, and your app will be free to respond to touch events.
What is important to notice here is that all of the code within this block will execute on the background thread, up until the point where the downloading/saving of the image was successful, at which point you might want to tell the activity indicator to stopAnimating, or apply the newly saved image to a UIImageView. Either way, these are updates to the UI, meaning you must dispatch back the the main thread using dispatch_get_main_queue() to perform them. Failing to do so results in undefined behavior, which may cause the UI to update after an unexpected period of time, or may even cause a crash. Always make sure you move back to the main thread before performing UI updates.
// Start the activity indicator before moving off the main thread
[self.activityIndicator startAnimating];
// Move off the main thread to start our blocking tasks.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Create the image URL from a known string.
NSURL *imageURL = [NSURL URLWithString:#"http://www.google.com/images/srpr/logo3w.png"];
NSError *downloadError = nil;
// Create an NSData object from the contents of the given URL.
NSData *imageData = [NSData dataWithContentsOfURL:imageURL
options:kNilOptions
error:&downloadError];
// ALWAYS utilize the error parameter!
if (downloadError) {
// Something went wrong downloading the image. Figure out what went wrong and handle the error.
// Don't forget to return to the main thread if you plan on doing UI updates here as well.
dispatch_async(dispatch_get_main_queue(), ^{
[self.activityIndicator stopAnimating];
NSLog(#"%#",[downloadError localizedDescription]);
});
} else {
// Get the path of the application's documents directory.
NSURL *documentsDirectoryURL = [self documentsDirectoryURL];
// Append the desired file name to the documents directory path.
NSURL *saveLocation = [documentsDirectoryURL URLByAppendingPathComponent:#"GCD.png"];
NSError *saveError = nil;
BOOL writeWasSuccessful = [imageData writeToURL:saveLocation
options:kNilOptions
error:&saveError];
// Successful or not we need to stop the activity indicator, so switch back the the main thread.
dispatch_async(dispatch_get_main_queue(), ^{
// Now that we're back on the main thread, you can make changes to the UI.
// This is where you might display the saved image in some image view, or
// stop the activity indicator.
// Check if saving the file was successful, once again, utilizing the error parameter.
if (writeWasSuccessful) {
// Get the saved image data from the file.
NSData *imageData = [NSData dataWithContentsOfURL:saveLocation];
// Set the imageView's image to the image we just saved.
self.imageView.image = [UIImage imageWithData:imageData];
} else {
NSLog(#"%#",[saveError localizedDescription]);
// Something went wrong saving the file. Figure out what went wrong and handle the error.
}
[self.activityIndicator stopAnimating];
});
}
});
Now keep in mind, that the method shown above is still not an ideal solution considering it can't be cancelled prematurely, it gives you no indication of the progress of the download, it can't handle any kind of authentication challenge, it can't be given a specific timeout interval, etc. (lots and lots of reasons). I'll cover a few of the better options below.
In these examples, I'll only be covering solutions for apps targeting iOS 7 and up considering (at time of writing) iOS 8 is the current major release, and Apple is suggesting only supporting versions N and N-1. If you need to support older iOS versions, I recommend looking into the NSURLConnection class, as well as the 1.0 version of AFNetworking. If you look at the revision history of this answer, you can find basic examples using NSURLConnection and ASIHTTPRequest, although it should be noted that ASIHTTPRequest is no longer being maintained, and should not be used for new projects.
NSURLSession
Lets start with NSURLSession, which was introduced in iOS 7, and greatly improves the ease with which networking can be done in iOS. With NSURLSession, you can easily perform asynchronous HTTP requests with a callback block and handle authentication challenges with its delegate. But what makes this class really special is that it also allows for download tasks to continue running even if the application is sent to the background, gets terminated, or even crashes. Here's a basic example of its usage.
// Start the activity indicator before starting the download task.
[self.activityIndicator startAnimating];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// Use a session with a custom configuration
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
// Create the image URL from some known string.
NSURL *imageURL = [NSURL URLWithString:#"http://www.google.com/images/srpr/logo3w.png"];
// Create the download task passing in the URL of the image.
NSURLSessionDownloadTask *task = [session downloadTaskWithURL:imageURL completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
// Get information about the response if neccessary.
if (error) {
NSLog(#"%#",[error localizedDescription]);
// Something went wrong downloading the image. Figure out what went wrong and handle the error.
// Don't forget to return to the main thread if you plan on doing UI updates here as well.
dispatch_async(dispatch_get_main_queue(), ^{
[self.activityIndicator stopAnimating];
});
} else {
NSError *openDataError = nil;
NSData *downloadedData = [NSData dataWithContentsOfURL:location
options:kNilOptions
error:&openDataError];
if (openDataError) {
// Something went wrong opening the downloaded data. Figure out what went wrong and handle the error.
// Don't forget to return to the main thread if you plan on doing UI updates here as well.
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"%#",[openDataError localizedDescription]);
[self.activityIndicator stopAnimating];
});
} else {
// Get the path of the application's documents directory.
NSURL *documentsDirectoryURL = [self documentsDirectoryURL];
// Append the desired file name to the documents directory path.
NSURL *saveLocation = [documentsDirectoryURL URLByAppendingPathComponent:#"NSURLSession.png"];
NSError *saveError = nil;
BOOL writeWasSuccessful = [downloadedData writeToURL:saveLocation
options:kNilOptions
error:&saveError];
// Successful or not we need to stop the activity indicator, so switch back the the main thread.
dispatch_async(dispatch_get_main_queue(), ^{
// Now that we're back on the main thread, you can make changes to the UI.
// This is where you might display the saved image in some image view, or
// stop the activity indicator.
// Check if saving the file was successful, once again, utilizing the error parameter.
if (writeWasSuccessful) {
// Get the saved image data from the file.
NSData *imageData = [NSData dataWithContentsOfURL:saveLocation];
// Set the imageView's image to the image we just saved.
self.imageView.image = [UIImage imageWithData:imageData];
} else {
NSLog(#"%#",[saveError localizedDescription]);
// Something went wrong saving the file. Figure out what went wrong and handle the error.
}
[self.activityIndicator stopAnimating];
});
}
}
}];
// Tell the download task to resume (start).
[task resume];
From this you'll notice that the downloadTaskWithURL: completionHandler: method returns an instance of NSURLSessionDownloadTask, on which an instance method -[NSURLSessionTask resume] is called. This is the method that actually tells the download task to start. This means that you can spin up your download task, and if desired, hold off on starting it (if needed). This also means that as long as you store a reference to the task, you can also utilize its cancel and suspend methods to cancel or pause the task if need be.
What's really cool about NSURLSessionTasks is that with a little bit of KVO, you can monitor the values of its countOfBytesExpectedToReceive and countOfBytesReceived properties, feed these values to an NSByteCountFormatter, and easily create a download progress indicator to your user with human readable units (e.g. 42 KB of 100 KB).
Before I move away from NSURLSession though, I'd like to point out that the ugliness of having to dispatch_async back to the main threads at several different points in the download's callback block can be avoided. If you chose to go this route, you can initialize the session with its initializer that allows you to specify the delegate, as well as the delegate queue. This will require you to use the delegate pattern instead of the callback blocks, but this may be beneficial because it is the only way to support background downloads.
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
AFNetworking 2.0
If you've never heard of AFNetworking, it is IMHO the end-all of networking libraries. It was created for Objective-C, but it works in Swift as well. In the words of its author:
AFNetworking is a delightful networking library for iOS and Mac OS X. It's built on top of the Foundation URL Loading System, extending the powerful high-level networking abstractions built into Cocoa. It has a modular architecture with well-designed, feature-rich APIs that are a joy to use.
AFNetworking 2.0 supports iOS 6 and up, but in this example, I will be using its AFHTTPSessionManager class, which requires iOS 7 and up due to its usage of all the new APIs around the NSURLSession class. This will become obvious when you read the example below, which shares a lot of code with the NSURLSession example above.
There are a few differences that I'd like to point out though. To start off, instead of creating your own NSURLSession, you'll create an instance of AFURLSessionManager, which will internally manage a NSURLSession. Doing so allows you take advantage of some of its convenience methods like -[AFURLSessionManager downloadTaskWithRequest:progress:destination:completionHandler:]. What is interesting about this method is that it lets you fairly concisely create a download task with a given destination file path, a completion block, and an input for an NSProgress pointer, on which you can observe information about the progress of the download. Here's an example.
// Use the default session configuration for the manager (background downloads must use the delegate APIs)
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// Use AFNetworking's NSURLSessionManager to manage a NSURLSession.
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
// Create the image URL from some known string.
NSURL *imageURL = [NSURL URLWithString:#"http://www.google.com/images/srpr/logo3w.png"];
// Create a request object for the given URL.
NSURLRequest *request = [NSURLRequest requestWithURL:imageURL];
// Create a pointer for a NSProgress object to be used to determining download progress.
NSProgress *progress = nil;
// Create the callback block responsible for determining the location to save the downloaded file to.
NSURL *(^destinationBlock)(NSURL *targetPath, NSURLResponse *response) = ^NSURL *(NSURL *targetPath, NSURLResponse *response) {
// Get the path of the application's documents directory.
NSURL *documentsDirectoryURL = [self documentsDirectoryURL];
NSURL *saveLocation = nil;
// Check if the response contains a suggested file name
if (response.suggestedFilename) {
// Append the suggested file name to the documents directory path.
saveLocation = [documentsDirectoryURL URLByAppendingPathComponent:response.suggestedFilename];
} else {
// Append the desired file name to the documents directory path.
saveLocation = [documentsDirectoryURL URLByAppendingPathComponent:#"AFNetworking.png"];
}
return saveLocation;
};
// Create the completion block that will be called when the image is done downloading/saving.
void (^completionBlock)(NSURLResponse *response, NSURL *filePath, NSError *error) = ^void (NSURLResponse *response, NSURL *filePath, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
// There is no longer any reason to observe progress, the download has finished or cancelled.
[progress removeObserver:self
forKeyPath:NSStringFromSelector(#selector(fractionCompleted))];
if (error) {
NSLog(#"%#",error.localizedDescription);
// Something went wrong downloading or saving the file. Figure out what went wrong and handle the error.
} else {
// Get the data for the image we just saved.
NSData *imageData = [NSData dataWithContentsOfURL:filePath];
// Get a UIImage object from the image data.
self.imageView.image = [UIImage imageWithData:imageData];
}
});
};
// Create the download task for the image.
NSURLSessionDownloadTask *task = [manager downloadTaskWithRequest:request
progress:&progress
destination:destinationBlock
completionHandler:completionBlock];
// Start the download task.
[task resume];
// Begin observing changes to the download task's progress to display to the user.
[progress addObserver:self
forKeyPath:NSStringFromSelector(#selector(fractionCompleted))
options:NSKeyValueObservingOptionNew
context:NULL];
Of course since we've added the class containing this code as an observer to one of the NSProgress instance's properties, you'll have to implement the -[NSObject observeValueForKeyPath:ofObject:change:context:] method. In this case, I've included an example of how you might update a progress label to display the download's progress. It's really easy. NSProgress has an instance method localizedDescription which will display progress information in a localized, human readable format.
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
// We only care about updates to fractionCompleted
if ([keyPath isEqualToString:NSStringFromSelector(#selector(fractionCompleted))]) {
NSProgress *progress = (NSProgress *)object;
// localizedDescription gives a string appropriate for display to the user, i.e. "42% completed"
self.progressLabel.text = progress.localizedDescription;
} else {
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
Don't forget, if you want to use AFNetworking in your project, you'll need to follow its installation instructions and be sure to #import <AFNetworking/AFNetworking.h>.
Alamofire
And finally, I'd like to give a final example using Alamofire. This is a the library that makes networking in Swift a cake-walk. I'm out of characters to go into great detail about the contents of this sample, but it does pretty much the same thing as the last examples, just in an arguably more beautiful way.
// Create the destination closure to pass to the download request. I haven't done anything with them
// here but you can utilize the parameters to make adjustments to the file name if neccessary.
let destination = { (url: NSURL!, response: NSHTTPURLResponse!) -> NSURL in
var error: NSError?
// Get the documents directory
let documentsDirectory = NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory,
inDomain: .UserDomainMask,
appropriateForURL: nil,
create: false,
error: &error
)
if let error = error {
// This could be bad. Make sure you have a backup plan for where to save the image.
println("\(error.localizedDescription)")
}
// Return a destination of .../Documents/Alamofire.png
return documentsDirectory!.URLByAppendingPathComponent("Alamofire.png")
}
Alamofire.download(.GET, "http://www.google.com/images/srpr/logo3w.png", destination)
.validate(statusCode: 200..<299) // Require the HTTP status code to be in the Successful range.
.validate(contentType: ["image/png"]) // Require the content type to be image/png.
.progress { (bytesRead, totalBytesRead, totalBytesExpectedToRead) in
// Create an NSProgress object to represent the progress of the download for the user.
let progress = NSProgress(totalUnitCount: totalBytesExpectedToRead)
progress.completedUnitCount = totalBytesRead
dispatch_async(dispatch_get_main_queue()) {
// Move back to the main thread and update some progress label to show the user the download is in progress.
self.progressLabel.text = progress.localizedDescription
}
}
.response { (request, response, _, error) in
if error != nil {
// Something went wrong. Handle the error.
} else {
// Open the newly saved image data.
if let imageData = NSData(contentsOfURL: destination(nil, nil)) {
dispatch_async(dispatch_get_main_queue()) {
// Move back to the main thread and add the image to your image view.
self.imageView.image = UIImage(data: imageData)
}
}
}
}
Asynchronous downloaded images with caching
Asynchronous downloaded images with caching
Here is one more repos which can be used to download images in background
You cannot save anything inside the app's bundle, but you can use +[NSData dataWithContentsOfURL:] to store the image in your app's documents directory, e.g.:
NSData *imageData = [NSData dataWithContentsOfURL:myImageURL];
NSString *imagePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:#"/myImage.png"];
[imageData writeToFile:imagePath atomically:YES];
Not exactly permanent, but it stays there at least until the user deletes the app.
That's the main concept. Have fun ;)
NSURL *url = [NSURL URLWithString:#"http://example.com/yourImage.png"];
NSData *data = [NSData dataWithContentsOfURL:url];
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
path = [path stringByAppendingString:#"/yourLocalImage.png"];
[data writeToFile:path atomically:YES];
Since we are on IO5 now, you no longer need to write images to disk neccessarily.
You are now able to set "allow external storage" on an coredata binary attribute.
According to apples release notes it means the following:
Small data values like image thumbnails may be efficiently stored in a
database, but large photos or other media are best handled directly by
the file system. You can now specify that the value of a managed
object attribute may be stored as an external record - see setAllowsExternalBinaryDataStorage:
When enabled, Core Data heuristically decides on a per-value basis if
it should save the data directly in the database or store a URI to a
separate file which it manages for you. You cannot query based on the
contents of a binary data property if you use this option.
As other people said, there are many cases in which you should download a picture in the background thread without blocking the user interface
In this cases my favorite solution is to use a convenient method with blocks, like this one: (credit -> iOS: How To Download Images Asynchronously (And Make Your UITableView Scroll Fast))
- (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if ( !error )
{
UIImage *image = [[UIImage alloc] initWithData:data];
completionBlock(YES,image);
} else{
completionBlock(NO,nil);
}
}];
}
And call it like
NSURL *imageUrl = //...
[[MyUtilManager sharedInstance] downloadImageWithURL:[NSURL URLWithString:imageURL] completionBlock:^(BOOL succeeded, UIImage *image) {
//Here you can save the image permanently, update UI and do what you want...
}];
Here's how I download an ad banner. It's best to do it in the background if you're downloading a large image or a bunch of images.
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelectorInBackground:#selector(loadImageIntoMemory) withObject:nil];
}
- (void)loadImageIntoMemory {
NSString *temp_Image_String = [[NSString alloc] initWithFormat:#"http://yourwebsite.com/MyImageName.jpg"];
NSURL *url_For_Ad_Image = [[NSURL alloc] initWithString:temp_Image_String];
NSData *data_For_Ad_Image = [[NSData alloc] initWithContentsOfURL:url_For_Ad_Image];
UIImage *temp_Ad_Image = [[UIImage alloc] initWithData:data_For_Ad_Image];
[self saveImage:temp_Ad_Image];
UIImageView *imageViewForAdImages = [[UIImageView alloc] init];
imageViewForAdImages.frame = CGRectMake(0, 0, 320, 50);
imageViewForAdImages.image = [self loadImage];
[self.view addSubview:imageViewForAdImages];
}
- (void)saveImage: (UIImage*)image {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString* path = [documentsDirectory stringByAppendingPathComponent: #"MyImageName.jpg" ];
NSData* data = UIImagePNGRepresentation(image);
[data writeToFile:path atomically:YES];
}
- (UIImage*)loadImage {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString* path = [documentsDirectory stringByAppendingPathComponent:#"MyImageName.jpg" ];
UIImage* image = [UIImage imageWithContentsOfFile:path];
return image;
}
Here is code to download an image asynchronously from url and then save where you want in objective-c:->
+ (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if ( !error )
{
UIImage *image = [[UIImage alloc] initWithData:data];
completionBlock(YES,image);
} else{
completionBlock(NO,nil);
}
}];
}
If you are using AFNetworking library to download image and that images are using in UITableview then You can use below code in cellForRowAtIndexPath
[self setImageWithURL:user.user_ProfilePicturePath toControl:cell.imgView];
-(void)setImageWithURL:(NSURL*)url toControl:(id)ctrl
{
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFImageRequestOperation *operation = [AFImageRequestOperation imageRequestOperationWithRequest:request imageProcessingBlock:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
if (image) {
if([ctrl isKindOfClass:[UIButton class]])
{
UIButton btn =(UIButton)ctrl;
[btn setBackgroundImage:image forState:UIControlStateNormal];
}
else
{
UIImageView imgView = (UIImageView)ctrl;
imgView.image = image;
}
}
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(#"No Image");
}];
[operation start];}
You can download image without blocking UI with using NSURLSessionDataTask.
+(void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
{
NSURLSessionDataTask* _sessionTask = [[NSURLSession sharedSession] dataTaskWithRequest:[NSURLRequest requestWithURL:url]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error != nil)
{
if ([error code] == NSURLErrorAppTransportSecurityRequiresSecureConnection)
{
completionBlock(NO,nil);
}
}
else
{
[[NSOperationQueue mainQueue] addOperationWithBlock: ^{
dispatch_async(dispatch_get_main_queue(), ^{
UIImage *image = [[UIImage alloc] initWithData:data];
completionBlock(YES,image);
});
}];
}
}];
[_sessionTask resume];
}
Here is a Swift 5 solution for downloading and saving an image or in general a file to the documents directory by using Alamofire:
func dowloadAndSaveFile(from url: URL) {
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
documentsURL.appendPathComponent(url.lastPathComponent)
return (documentsURL, [.removePreviousFile])
}
let request = SessionManager.default.download(url, method: .get, to: destination)
request.validate().responseData { response in
switch response.result {
case .success:
if let destinationURL = response.destinationURL {
print(destinationURL)
}
case .failure(let error):
print(error.localizedDescription)
}
}
}