show pragress bar while downloading file - ios

Hi in my application I have a file in URL I want download to my document folder in my application while downloading the file i want to show the progress bar. So I have used the MBProgressHUD for that but its not working properly its showing late please tell me how to resolve this one.
My code:
- (IBAction)down:(id)sender {
UIButton *btn = (UIButton *)sender;
spinner = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
spinner.mode = MBProgressHUDModeCustomView;
[spinner setLabelText:#"downloading....."];
[spinner setLabelFont:[UIFont systemFontOfSize:15]];
[spinner show:YES];
NSURL *url = [NSURL URLWithString:fileurl];
NSData *urlData = [NSData dataWithContentsOfURL:url];
if ( urlData )
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [NSString stringWithFormat:#"%#/%#", documentsDirectory,name];
[urlData writeToFile:filePath atomically:YES];
}
[activityIndicatorObject stopAnimating];
[spinner hide:YES];
[spinner removeFromSuperViewOnHide];
}
I have used the above code its taking too much time to show the MBProgressHUD Please tell me how to resolve this issue I have stuck here for time.
Thanks..

You have to change in your code as below..
- (IBAction)down:(id)sender {
UIButton *btn = (UIButton *)sender;
//spinner = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
//spinner.mode = MBProgressHUDModeCustomView;
//[spinner setLabelText:#"downloading....."];
//[spinner setLabelFont:[UIFont systemFontOfSize:15]];
//[spinner show:YES];
NSURL *url = [NSURL URLWithString:fileurl];
NSData *urlData = [NSData dataWithContentsOfURL:url];
if ( urlData )
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [NSString stringWithFormat:#"%#/%#", documentsDirectory,name];
[urlData writeToFile:filePath atomically:YES];
}
//[activityIndicatorObject stopAnimating];
//[spinner hide:YES];
//[spinner removeFromSuperViewOnHide];
[MBProgressHUD hideHUDForView:self.view animated:YES];
}
Enjoy Code..

I suspect what you are experiencing are concurrency issues with the above code. I'm assuming the URL you are fetching is from over a network. The NSData method dataWithContentsOfURL: is blocking the main thread before the operating system has a chance to call setNeedsDisplay on the spinner, which also happens on the main thread, and this could explain why it's showing up late.
The solution would be to make some sort of asynchronous request, to fetch the file, with a completion handler, so your application can do tasks etc upon completion, (like stopping the activity indicator, persisting the file to local storage etc).
This is what the documentation has has to say about the dataWithContentsOfURL: of NSData class :-
Important: Do not use this synchronous method to request network-based URLs. For network-based URLs, this method can block the current thread for tens of seconds on a slow network, resulting in a poor user experience, and in iOS, may cause your app to be terminated.
Instead, for non-file URLs, consider using the dataTaskWithURL:completionHandler: method of the NSSession class.

Related

uidocumentinteractioncontroller sending file URL besides image in Viber

I am using a standard UIDocumentInteractionController in my app, and provide it with a local image URL.
However, once Viber is selected, it first sends a plain text message with the file URL and then the image. I only want to send the image ofcourse.
I see that it's working allright from the native Gallery, so it's obviously my fault.
here's the code:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString *docs = [paths objectAtIndex:0];
NSString* path = [docs stringByAppendingFormat:#"/tempName.jpg"];
MBProgressHUD *HUD = [[MBProgressHUD alloc] initWithView:imgPicker.view];
HUD.mode = MBProgressHUDModeAnnularDeterminate;
[imgPicker.view addSubview:HUD];
[HUD show:YES];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData* imageData = [NSData dataWithData:UIImageJPEGRepresentation(image, 80)];
NSError *writeError = nil;
[imageData writeToFile:path options:NSDataWritingAtomic error:&writeError];
_documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL URLWithString:path]];
[_documentInteractionController setUTI:(NSString *)kUTTypeJPEG];
_documentInteractionController.delegate = self;
dispatch_async(dispatch_get_main_queue(), ^{
//Your main thread code goes in here
[HUD removeFromSuperview];
[_documentInteractionController presentOptionsMenuFromRect:CGRectZero inView:imgPicker.view animated:YES]; });
});

NSURL download file, indicators shows too late

Hi I got the following code and want do show an ActivityIndicator while downloading. But the indicator shows only when the download has finished?
_fanDownloadIndicator.hidden = NO;
NSLog(#"batzn");
NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0];
NSString *file = [documentPath stringByAppendingPathComponent:#"fan_new.mp3"];
BOOL fileExists = [[NSFileManager defaultManager]fileExistsAtPath:file];
if(fileExists == NO) {
NSURL *downurl = [NSURL URLWithString:url];
NSData *data = [NSData dataWithContentsOfURL:downurl];
if ([data writeToFile:file atomically:YES]) {
NSLog(#"downloaded fan");
Issue
You are doing the download task on main thread, and you wait until the thread ends before you show your loading view.
Solution
You need to start downloading task on background thread by using dispatch_async. Check out the code below
_fanDownloadIndicator.hidden = NO;
NSLog(#"batzn");
NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0];
NSString *file = [documentPath stringByAppendingPathComponent:#"fan_new.mp3"];
BOOL fileExists = [[NSFileManager defaultManager]fileExistsAtPath:file];
if(fileExists == NO) {
//Show your loading view here
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//Do your downloading here on background thread
NSURL *downurl = [NSURL URLWithString:url];
NSData *data = [NSData dataWithContentsOfURL:downurl];
dispatch_async(dispatch_get_main_queue(), ^{
if ([data writeToFile:file atomically:YES]) {
//Hide your loading view
NSLog(#"downloaded fan");
}
});
});
}
Also I'll provide a suggestion for your loading view which is MBProgressHUD. Here is what you can do with it
//Show loading form here
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// Do something...
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
});
Update
If you are dealing with downloading images, i should recommend this library:
SDWebImage
It also offers a third party to show activity indicator.

How to make a URL load request timeout?

Im using this tutorial to load an image asynchronously in my app. I modified that code so the picture saves to the iPhone's local files and can be loaded while offline. I want to make it so this load request times out after a certain interval, possibly 15-20 seconds, and loads the saved file instead of downloading a new one. I found ways to make a web view time out, but Im not sure how to go about doing this using the asynchronous method. How can I make a timeout request for the way that this code loads the url?
Thanks
Edit: I want to make it time out if it is unable to connect to the website and also if the downloading of the picture takes too long.
- (void)viewDidLoad
{
[super viewDidLoad];
NSOperationQueue *queue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc]
initWithTarget:self
selector:#selector(loadImage)
object:nil];
[queue addOperation:operation];
}
- (void)loadImage {
NSData* imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:#"http://www.TestURL.com/test.jpg"]];
UIImage* image = [[UIImage alloc] initWithData:imageData];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *localFilePath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"test.jpg"]];
[imageData writeToFile:localFilePath atomically:YES];
[self performSelectorOnMainThread:#selector(displayImage:) withObject:image waitUntilDone:YES];
}
if you use NSData* imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:#"http://www.TestURL.jpg"]]; You are creating a synchronous connection, so You can´t cancel it. You need to wait till the end.
You should implement an asynchronous download using NSURLConnection as explained in How can I deal with connection problem when I use NSData?
Now, I am a newbie at this to, and I realize that this is a way to do it but really doesn't answer the question, but how I handled this situation was to write a method that first checked locally for the image, and if it wasn't there, load it from the web and save it locally, so it was there the next time. Here is some code.
- (UIImage *)checkForLocalImageThenSave:(NSString *)name fromStringURL:(NSString *)url {
NSLog(#"********** Start loading image **********\n\n");
UIImage *image = [[UIImage alloc] init];
NSString *localDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *profilePicName = [NSString stringWithFormat:#"/%#", name];
NSString *profilePicNameOnline = [NSString stringWithFormat:#"%#", url];
NSString *directoryWithProfilePicName = [localDirectory stringByAppendingString:profilePicName];
NSLog(#"Looking for file: %#", directoryWithProfilePicName);
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:directoryWithProfilePicName];
if (fileExists) {
NSLog(#"File exists. Load: %#\n\n", directoryWithProfilePicName);
image = [[UIImage alloc] initWithContentsOfFile: directoryWithProfilePicName];
NSLog(#"********** Loading image done **********\n\n");
} else {
NSLog(#"File does not exist. Save: %#", directoryWithProfilePicName);
// TO SAVE A JPEG FILE
NSData *imageWithURL = [[NSData alloc] initWithContentsOfURL:[[NSURL alloc] initWithString:profilePicNameOnline]];
NSLog(#"File at? %#", profilePicNameOnline);
image = [[UIImage alloc] initWithData:imageWithURL];
NSString *jpegFilePath = directoryWithProfilePicName;
NSData *data = [NSData dataWithData:UIImageJPEGRepresentation(image, 1.0f)];//1.0f = 100% quality
[data writeToFile:jpegFilePath atomically:YES];
NSLog(#"Saving image done.");
}
return image;
}

Can I async UIImageJPEGRepresentation writeToFile?

I am doing the following when I get an image back from the server. But this code is bringing my app to its knees. It freezes up the UI.
Can this be run on a background thread in iOS? Can I use async?
if (![NSString isEmpty:user.avatarURL])
{
NSString *pathToImage = user.avatarURL;
NSURL *url = [NSURL URLWithString:pathToImage];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [[UIImage alloc] initWithData:data];
[[NSUserDefaults standardUserDefaults] setObject:UIImageJPEGRepresentation(image, 1) forKey:kUserImage];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString* path = [documentsDirectory stringByAppendingPathComponent:#"userAvatar.png" ];
NSData* jpegdata = UIImageJPEGRepresentation(image, 1);
[jpegdata writeToFile:path atomically:YES];
}
As #valentin says, you can do everything inside the if() statement in a dispatch_async() call.
Note I suspect what’s probably slowing you down is actually the -dataWithContentsOfURL:, not the UIImageJPEGRepresentation(), so you’ll want to make sure that’s inside your dispatch_async, not outside.
Also, I’m not clear why you’re decompressing the data into an image, then re-compressing it. You’re going to get artifacts doing this, and with most services the avatar image is going to be compressed anyhow.
I’d do the following:
if (user.avatarURL)
dispatch_async(dispatch_get_main_queue(), ^{
NSURL *const imageURL = [NSURL URLWithString:user.avatarURL];
if (!imageURL)
return;
NSData *const imageDdata = [NSData dataWithContentsOfURL:imageURL];
if (!imageDdata.length)
return;
[[NSUserDefaults standardUserDefaults] setObject:imageDdata forKey:kUserImage];
[imageDdata writeToFile:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject stringByAppendingPathComponent:#"userAvatar.png"] atomically:YES];
});
The line which is doing most damage is:
NSData *data = [NSData dataWithContentsOfURL:url];
Because it does the download from the network. Nothing in your code updates the UI so it can all be run on a background thread. Just ensure that if you post a notification or subsequently update the UI after the image is saved that you switch back to the main thread.

iOS code skipping over dispatch_async

I need to download data from a URL (this prints the data in JSON format) and store it in a "configuration" file for the app in the app's AppDelegate.m file. When I run the app, it simply skips over the dispatch_async code for some reason. Why is this happening and how do I fix this ?
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//Download the config.json file
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSString *configFileUrl = #"http://webserviceurl";
//NSString *downloadToFile = #"Configuration.json";
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:configFileUrl]];
[self performSelectorOnMainThread:#selector(writeDataToConfigurationJsonFile:) withObject:data waitUntilDone:YES];
});
//More code below
And this is where I am writing the data to a file in the Documents directory of the app:
-(void)writeDataToConfigurationJsonFile:(NSData*)jsonData{
NSString *content = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
//get the documents directory:
NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
//make a file name to write the data to using the documents directory:
NSString *fileName = [NSString stringWithFormat:#"%#/Configuration.json", documentsDirectory];
//save content to the documents directory
[content writeToFile:fileName
atomically:YES
encoding:NSUTF8StringEncoding
error:nil];
}
performSelectorOnMainThread is a run loop method, you need to use:
dispatch_async(dispatch_get_main_queue(), ^{/*code*/});
You can nest a call to dispatch_sync() within dispatch_async() to ensure after the data is downloaded, the data is written synchronously on the main thread.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//Download the config.json file
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSString *configFileUrl = #"http://webserviceurl";
//NSString *downloadToFile = #"Configuration.json";
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:configFileUrl]];
dispatch_sync(dispatch_get_main_queue(), ^{
[self writeDataToConfigurationJsonFile:data];
});
});
}
When you're asynchronously dispatching a thread, create a new serial/concurrent queue.
And for the synchronous dispatch, go back to the main queue (try without using 'waitUntilDone:YES'):
dispatch_async(dispatch_queue_create("com.yourOrgName", DISPATCH_QUEUE_SERIAL), ^{
NSString *configFileUrl = #"http://webserviceurl";
//NSString *downloadToFile = #"Configuration.json";
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:configFileUrl]];
dispatch_sync(dispatch_get_main_queue(), ^ {
[self performSelector:#selector(writeDataToConfigurationJsonFile:) withObject:data afterDelay:0.0f];
});
});
For the purpose of this application, the best way was to use a synchronous request rather than an asynchronous request. Here's the final chunk of code -
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"some url"]];
NSError *error = nil;
NSURLResponse *response = nil;
NSData *receivedData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSString *string = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
NSLog(#"response");
NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
//make a file name to write the data to using the documents directory:
NSString *fileName = [NSString stringWithFormat:#"%#/Configuration.json", documentsDirectory];
//save content to the documents directory
[string writeToFile:fileName
atomically:YES
encoding:NSUTF8StringEncoding
error:nil];

Resources