iOS download and save image inside app - iphone
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)
}
}
}
Related
How make update of my UILabel in xcode
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.
How to run a thread in background of application which will not affect my application user interface
I have made an application for IOS, in which I am using sqlite database, database is local in application. Now I have given the functionality the application is downloading data from internet & put it in local database & show infront of user. I have given this functionality on - (void)viewDidLoad so that when application is downloading data, it stop working till it finish downloading part, for this user need to wait to interact with application. Now I wants to functionality a thread run in background of application which will connect the internet & update the application without interfering user. please help me. My code of download & save image is this: -(void)Download_save_images:(NSString *)imagesURLPath :(NSString *)image_name { NSMutableString *theString = [NSMutableString string]; // Get an image from the URL below UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:NSURL URLWithString:imagesURLPath]]]; NSLog(#"%f,%f",image.size.width,image.size.height); NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; // If you go to the folder below, you will find those pictures NSLog(#"%#",docDir); [theString appendString:#"%#/"]; [theString appendString:image_name]; NSLog(#"%#",theString); NSLog(#"saving png"); NSString *pngFilePath = [NSString stringWithFormat:theString,docDir]; NSData *data1 = [NSData dataWithData:UIImagePNGRepresentation(image)]; [data1 writeToFile:pngFilePath atomically:YES]; NSLog(#"saving image done"); [image release]; // [theString release]; } when i am debugging application i seen my application taking more time at below line: UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:NSURL URLWithString:imagesURLPath]]];
You can also use NSOperationQueue and NSBlockOperation if you find GCD difficult. NSBlockOperation *operation=[[NSBlockOperation alloc] init]; [operation addExecutionBlock:^{ //Your code goes here }]; NSOperationQueue *queue=[[NSOperationQueue alloc] init]; [queue addOperation:operation]; In GCD You could achieve the same using dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ //Your Code to download goes here dispatch_async(dispatch_get_main_queue(), ^{ //your code to update UI if any goes here }); }); Use either of the API Depending upon your needs. Check this thread which discuss about NSOperationQueue vs GCD for more info.
Questions like these were asked hundreds of times before. I suggest you do a quick search on this. Also there is a topic in apple documentation covering this area thoroughly. here Basically you can do this with operation queues or dispatch queues. There are some code snippets given above by Avi & Amar. I'd like to add something since you seem to be unfamiliar with the topic & you mentioned there are web requesting involved. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ // Dont DIRECTLY request your NSURL Connection in this part because it will never return data to the delegate... // Remember your network requests like NSURLConnections must be invoked in mainqueue // So what ever method you're invoking for NSURLConnection it should be on the main queue dispatch_async(dispatch_get_main_queue(), ^{ // this will run in main thread. update your UI here. }); }); I've given small example below. you can generalise the idea to match your needs. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ // Do your background work here // Your network request must be on main queue. this can be raw code like this or method. which ever it is same scenario. NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.google.com"]]; dispatch_async(dispatch_get_main_queue(), ^{ [NSURLConnection sendAsynchronousRequest:req queue:[NSOperationQueue currentQueue] completionHandler:^(NSURLResponse *res, NSData *dat, NSError *err) { // procress data dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ // back ground work after network data received dispatch_async(dispatch_get_main_queue(), ^{ // finally update UI }); }); }]; }); });
You can us GCD, like that: dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ // put here your background code. This will run in background thread. dispatch_async(dispatch_get_main_queue(), ^{ // this will run in main thread. update your UI here. }); }) But you need to understand how blocks work for example. Try it.
Use GCD create your dispatch object dispatch_queue_t yourDispatch; yourDispatch=dispatch_queue_create("makeSlideFast",nil); then use it dispatch_async(yourDispatch,^{ //your code here //use this to make uichanges on main thread dispatch_async(dispatch_get_main_queue(), ^(void) { }); }); Do ui on main thread only by using dispatch_async(dispatch_get_main_queue(), ^(void) { }); Then release it dispatch_release(yourDispatch); Here is tutorial
There are a lot of ways: 1 . Grand Central Dispatch dispatch_async(dispatch_get_global_queue(0, 0), ^{ //Your code }); 2 . NSThread [NSThread detachNewThreadSelector:#selector(yourMethod:) toTarget:self withObject:parameterArray]; 3 . NSObject - performSelectorInBackground [self performSelectorInBackground:#selector(yourMethod:) withObject:parameterArray];
iOS 5 NSURLConnection - Making Multiple Connections with UI Feedback
I am currently designing an iOS 5 iPhone app that will use a .NET RESTful Web Service to provide data updates. When the application is initially installed, it will connect to the WS to download all the data in a JSON format. Thereafter, it will only perform updates. The WS provides a POST method for each table as GetAllTableRecords() and GetLastUpdatedTableRecords(). I am using iOS 5 and I've got the NSURLConnection and JSON serialization/deserialization working correctly with native libraries. Each WS POST method call is currently residing in its own Obj-C class with all of the delegate methods. Additionally, each class handles the local datastore inserts and updates. Each NSURLConnection is asynchronous and all of the WS calls are driven off of button events from view controllers. My questions are: Is this the right setup in terms of code encapsulation and reuse? How do I handle making multiple WS calls while keeping the user informed via the UI? Currently there are two tables to download. This means the app will call the WS twice to get the initial data and twice again during each refresh. I know that since each NSURLConnection is asynchronous, the connection will make the request but the UI will continue on while the delegate handles the data download. I've done some research into GCD and NSOperation/Queue but I don't know enough about either one to code a solution or know if that's even a correct solution. Any insight would be most helpful! Edit #1: What about providing real time updates back to the UI? The Mint app does something similar when updating transactions and accounts. They have a little status bar that pops up at the bottom while requests are made. Edit #2: Ok, I believe I've made some progress. We are using Story Boards and the entry point is the Login View/Controller. When the login button is clicked, a NSURLConnection is made to the webservice. If the response status code is 200 in connectionDidFinishLoading:(NSURLConnection *)connection, a segue is performed to go the Data Sync View. The purpose of this view is to either initialize or update the database while providing feedback to the user. Either updating or initializing requires two additional web service calls. Here's my DataSyncView.m: #synthesize pvStatus, lbStatus; // pvStatus = progress indicator // lbStatus = label - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view from its nib. } - (void)viewDidUnload { [super viewDidUnload]; // Release any retained subviews of the main view. // e.g. self.myOutlet = nil; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [self StartDataSync]; } - (void)StartDataSync { [lbStatus setText:#"Syncing data..."]; [pvStatus setProgress:0.0f]; // TODO: Determine if database is popuplated [self PerformInitialSync]; // Next screen [self performSegueWithIdentifier:#"SegueFromSync" sender:self]; } // Populates data store will data from web service - (void)PerformInitialSync { // Kicks off a series of synchronous requests [self DownloadAllEmployeeDataA]; } - (void)DownloadAllDataA { // Dictonary holds POST values NSMutableDictionary *reqDic = [NSMutableDictionary dictionary]; // Populate POST key/value pairs [reqDic setObject:passWord forKey:#"Password"]; [reqDic setObject:userName forKey:#"UserName"]; NSError *error = nil; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:reqDic options:NSJSONWritingPrettyPrinted error:&error]; // Convert dictionary to JSON NSString *requestJSON = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; // Declare Webservice URL, request, and return data NSURL *url = [[NSURL alloc] initWithString:#"http://wsurl/getalla"]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; NSData *postData = [NSData dataWithBytes:[requestJSON UTF8String] length:[requestJSON length]]; // Build the request [request setHTTPMethod:#"POST"]; [request setValue:[NSString stringWithFormat:#"%d", [postData length]] forHTTPHeaderField:#"Content-Length"]; [request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"]; [request setHTTPBody:postData]; [request setCachePolicy:NSURLRequestUseProtocolCachePolicy]; [request setTimeoutInterval:60.0]; NSURLResponse *response; [lbStatus setText:#"Downloading employee data..."]; [pvStatus setProgress:0.1f]; // Make the response NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; // If return data received if(returnData) { // Get the response and check the code NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response; int code = [httpResponse statusCode]; // Check to make sure successful code if (code == 200) { // Convert JSON objects to Core Data Entity // Update UIProgressView and Label // Call next WS call [self DownloadAllEmployeeDataA]; } } } - (void)DownloadAllDataB { // Same code as above but with different URL and entity } My problem that I am having is this: The UIProgressView and Label are not updating as the calls are being made. As I stated before, I don't even know if this is the best way to make these calls. It doesn't appear that I'm blocking the main thread but I could be wrong. Again, I'll pose the question: what is the best way to make multiple url calls while keeping the UI updated on the progress? Thanks!!!
// Make the response NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; In your question you said you made asynchronous load of url request. But in the above line of code you are making a synchronous request ? Is this the right setup in terms of code encapsulation and reuse? Looking at your code you are not adhering to MVC. Your View Controller shouldn't manage loading URL connections. You can create a class that does that and using delegates inform the view controller whether data is downloaded or failed to download,etc. How do I handle making multiple WS calls while keeping the user informed via the UI? If you want to make concurrent URL Connections then use NSOperation and NSOperationQueue. Try avoiding GCD ( Refer to WWDC 2010 Session 208 ). My problem that I am having is this: The UIProgressView and Label are not updating as the calls are being made. You are making a Synchronous URL request on main thread. As per your code UIProgressView shouldn't update. Refer URL Loading System Programming Guide Another comment I have is your method names, start method name with small letter. Rest of it looks fine. Coding Guidelines for Cocoa
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
NSURLConnection - how to wait for completion
Our iPhone app code currently uses NSURLConnection sendSynchronousRequest and that works fine except we need more visibility into the connection progress and caching so we're moving to an async NSURLConnection. What's the simplest way to wait for the async code to complete? Wrap it in a NSOperation/NSOperationQueue, performSelector..., or what? Thanks.
I'm answering this in case anyone else bumps into the issue in the future. Apple's URLCache sample code is a fine example of how this is done. You can find it at: iOS Developer Library - URLCache As John points out in the comment above - don't block/wait - notify.
To use NSURLConnection asynchronously you supply a delegate when you init it in initWithRequest:delegate:. The delegate should implement the NSURLConnection delegate methods. NSURLConnection processing takes place on another thread but the delegate methods are called on the thread that started the asynchronous load operation for the associated NSURLConnection object.
Apart from notifications mentioned prior, a common approach is to have the class that needs to know about the URL load finishing set itself as a delegate of the class that's handling the URL callbacks. Then when the URL load is finished the delegate is called and told the load has completed. Indeed, if you blocked the thread the connection would never go anywhere since it works on the same thread (yes, even if you are using the asynch methods).
I ran into this because our app used NSURLConnection sendSynchronousRequest in quite a few places where it made sense, like having some processing occurring on a background thread occasionally needing extra data to complete the processing. Something like this: // do some processing NSData * data = someCachedData; if (data = nil) { data = [NSURLConnection sendSynchronousRequest....] someCachedData = data; } // Use data for further processing If you have something like 3 different places in the same flow that do that, breaking it up into separate functions might not be desirable(or simply not doable if you have a large enough code base). At some point, we needed to have a delegate for our connections(to do SSL certificate pinning) and I went trolling the internet for solutions and everything was of the form: "just use async and don't fight the framework!". Well, sendSynchronousRequest exists for a reason, this is how to reproduce it with an underlying async connection: + (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse *__autoreleasing *)response error:(NSError *__autoreleasing *)error { static NSOperationQueue * requestsQueue; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ requestsQueue = [[NSOperationQueue alloc] init]; requestsQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount; }); NSCondition * waitLock = [NSCondition new]; [waitLock lock]; __block NSError * returnedError; __block NSURLResponse * returnedResponse; __block NSData * returnedData; __block BOOL done = NO; [NSURLConnection sendAsynchronousRequest:request queue:requestsQueue completionHandler:^(NSURLResponse * response, NSData * data, NSError * connectionError){ returnedError = connectionError; returnedResponse = response; returnedData = data; [waitLock lock]; done = YES; [waitLock signal]; [waitLock unlock]; }]; if (!done) { [waitLock wait]; } [waitLock unlock]; *response = returnedResponse; *error = returnedError; return returnedData; } Posted here in case anyone comes looking as I did. Note that NSURLConnection sendAsynchrounousRequest can be replaced by whatever way you use to send an async request, like creating an NSURLConnection object with a delegate or something.