Activity Indicator Multithreaded Execution - ios

When my code hits the web service call, the activity indicator does not show up, and the button freezes in the "selected" state. I would like the activity indicator to run while the web service call is made, so that the screen does not look like it's freezing.
Here is my code to start the activity Indicator:
[activityInd startAnimating];
Here is my code to make a web service call:
NSString *urlString = [NSString stringWithFormat:#"http://us.api.invisiblehand.co.uk/v1/products?query=%#&app_id=dad00cb7&app_key=ab386c3e1b99b58b876f237d77b4211a", [[searchedItem.text stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] stringByReplacingOccurrencesOfString:#"+" withString:#"%2B"]];
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:url];
NSDictionary *dataDictionary = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
NSArray *itemCallArray = [NSArray arrayWithArray:dataDictionary[#"results"]];
How do I make the Activity Indicator and the Web Service call run on two separate threads?

Don't rewrite your code, just run it in the background.
NSString *urlString = [NSString stringWithFormat:#"http://us.api.invisiblehand.co.uk/v1/products?query=%#&app_id=dad00cb7&app_key=ab386c3e1b99b58b876f237d77b4211a", [[searchedItem.text stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] stringByReplacingOccurrencesOfString:#"+" withString:#"%2B"]];
[activityInd startAnimating];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
//Background Thread
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:url];
NSDictionary *dataDictionary = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
NSArray *itemCallArray = [NSArray arrayWithArray:dataDictionary[#"results"]];
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run UI Updates
[activityInd stopAnimating];
});
});

Are you running web service call on the main thread. You should never run this on the main thread. This would freeze the GUI and the OS will terminate your app.
Run activity indicator on the main thread and run web service call in another by using blocks.
please let me know if you need more details on how to do this.

Related

Images hangs and takes too long to populate from service

I am trying to fetch images on a button click in Viewcontroller B .After the image is received as response,I use the url of images to populate the imageviews on viewcontroller.But,it is taking too long to get populated and also hangs while scrolling.Is there any way to scroll the view smoothly without freezing and get the response little early.
This line should not be in main queue
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:[[responseArr objectAtIndex:indexPath.row]valueForKey:#"CategoryImage"]]];
you may change to something like this
NSString *getImagePath = [documentsPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.jpeg",n1.Id]];
if([[NSFileManager defaultManager] fileExistsAtPath:getImagePath])
{
UIImage *img = [UIImage imageWithContentsOfFile:getImagePath];
cell.leftImageV.image =img;
[cell.activity stopAnimating];
cell.activity.hidden=YES;
}
else
{
[cell.activity startAnimating];
cell.activity.hidden=NO;
cell.leftImageV.image = nil;
NSLog(#"xdasdxsadxa %#",n1.mainImgStr);
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
__block NSData * imageData = nil;
dispatch_sync(concurrentQueue, ^{
imageData = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString:n1.mainImgStr]];
//Add the file name
[imageData writeToFile:getImagePath atomically:YES]; //Write the file
});
dispatch_sync(dispatch_get_main_queue(), ^{
if(imageData)
{
[self.collectionView reloadData];
}
});
});
}
Use dispatch async for image retrieve, namely, don't do it on main queue.
Apple Doc:
https://developer.apple.com/documentation/dispatch/1453057-dispatch_async

show pragress bar while downloading file

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.

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 Cache NSDATA json in iOS

I am using this code to fetch application screenshots of an application from appStore using only appID,
NSString *numericIDStr = appID;
NSString *urlStr = [NSString stringWithFormat:#"http://itunes.apple.com/lookup?id=%#", numericIDStr];
NSURL *url = [NSURL URLWithString:urlStr];
NSData *json = [NSData dataWithContentsOfURL:url];
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:json options:0 error:NULL];
NSArray *results = [dict objectForKey:#"results"];
NSDictionary *result = [results objectAtIndex:0];
myArray = [result objectForKey:#"screenshotUrls"];
I managed to cache screenshots by using SDWebImage and it works perfect, but I noticed that every time that I call this method it delays about 3-4 seconds, and on 3G its even more, about 10-15 seconds.
and other issue that my app crash when not connected to the internet, is there any way to kinda cache this method or something similar?
You can save your response(cache) using the following libraries on disk or in memory.
TMCache
or EGOCache
Use AFNetworking. It will take one line of code and do the caching for you.
[yourImage setImageWithURL:yourNSUrl];
That's it! It does all the caching for you.

Image URL JSON parse to UIImageView in app error

I'm trying to parse an image url through JSON in my iPhone application.
My json model is built like this:
{
"picture":"link_to_image.jpg",
"about":"about text here",
"name":"Name"
}
I use this code to parse the itemw in my app:
- (void)fetchedData:(NSData *)responseData
{
NSError *error;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:responseData
options:kNilOptions error:&error];
self.titleLabel.text = [json objectForKey:#"name"];
self.aboutText.text = [json objectForKey:#"about"];
self.profileImage.image = [json objectForKey:#"picture"];
}
And in ViewDidLoad I wrote this:
dispatch_queue_t queue = dispatch_get_global_queue
(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSData *data = [NSData dataWithContentsOfURL:
[NSURL URLWithString:#"link_to_my_json_file.php"]];
[self performSelectorOnMainThread:#selector(fetchedData:)
withObject:data waitUntilDone:YES];
});
I've connected the outlets to the items in my .xib-file and the title and about text are successfully parsed to the label and textview. But the image won't parse. The app keeps crashing when I try this for the image.
Can someone please explain what I'm doing wrong?
Thanks!
As #Hot Licks have mentioned in comments you are putting a NSString pointer into UIImage property. Following method should work.
- (void)fetchedData:(NSData *)responseData
{
NSError *error;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:responseData
options:kNilOptions error:&error];
self.titleLabel.text = [json objectForKey:#"name"];
self.aboutText.text = [json objectForKey:#"about"];
NSURL *URL = [NSURL URLWithString: [json objectForKey:#"picture"]];
dispatch_queue_t queue = dispatch_get_global_queue
(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSData *data = [NSData dataWithContentsOfURL: URL];
self.profileImage.image = [UIImage imageWithData: data];
});
}

Resources