In my application I'm calling a method asynchronously by a button press. The screen segues to a different view controller which is a table view.
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDownloadTask *task = [session downloadTaskWithRequest:request
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error)
{
NSData *data = [[NSData alloc] initWithContentsOfURL:location];
NSArray *array = [NSJSONSerialization JSONObjectWithData:data
options:0
error:&error];
self.yearBucket = [NSMutableArray array];
for (NSDictionary * dict in array) {
Year *year = [[Year alloc ]init];
year.yearName =[dict objectForKey:#"Year"];
year.speeches = [dict objectForKey:#"Speeches"];
[self.yearBucket addObject:year];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
...
The problem is that the UI moves into a blank table view, since the data takes time to load. How can I display and alert view on the next screen that dynamically displays the loading of the data and can is dismissed as soon as the view refreshes back to the main thread.
I would suggest you display an MVProgressHUD on your view whilst you load the data and then perform a [tableview reloadData] in order to update the data shown in the table.
You should probably also display a network activity indicator. This is simply good practise when network connectivity is occurring. I presume if your app relies on an internet connection you are checking for connectivity before allowing the user to begin a request?
Related
I'm new to Objective-C, just wondering how to use NSArray object outside from JSON.
For example:
NSURL *url = [NSURL URLWithString:#"http://acumen-locdef.elasticbeanstalk.com/service/countries"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSMutableArray *myFinalListArray = [[NSMutableArray alloc] init];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError)
{
if (data.length > 0 && connectionError == nil)
{
NSMutableArray *greeting = [NSJSONSerialization JSONObjectWithData:data
options:0
error:NULL];
for (NSDictionary *countryList in greeting) {
[myFinalListArray addObject:countryList[#"name"]];
}
}
NSLog(#"%#",myFinalListArray); //(This one showing all results..)
}];
NSLog(#"%#",myFinalListArray); //(This one giving empty result)
I have defined myFinalListArray and added objects in for loop.
If you use NSLog inside the loop or outside the loop it will show you results. But if I use this after }]; (after the code is ending.),
it's giving me empty array.
If you are accessing myFinalListArray in tableview then you can reload tableview inside the block after fetching data.
Or if you are accessing this array in some other task then you have to make notification call (have to add observer) and then post notification that will call some other method and access your array there and do your further stuff.
The block of code associated with sendAsynchronousRequest isn't executed until the network fetch has completed; this takes some time. While the network fetch is happening your code continues to execute, starting with the line immediately after sendAsynchronousRequest which is NSLog(#"%#",myFinalListArray); - but because the network operation hasn't completed you get an empty array.
In the block you need to include the code that you need to process the array, update your user interface or whatever (If you update UI make sure you dispatch the operation on the main thread)
This will work. You can try with this.
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray *myFinalListArray = [[NSMutableArray alloc] init];
//Pass here the reference the a array. It will return you the array of you county when downloaded complete.
[self getURLResponse:&myFinalListArray];
NSLog(#"countryArray:%#",myFinalListArray);
}
-(void)getURLResponse:(NSMutableArray**)countryArray{
NSURL *url = [NSURL URLWithString:#"http://acumen-locdef.elasticbeanstalk.com/service/countries"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSMutableArray *myFinalListArray = [[NSMutableArray alloc] init];
NSURLResponse *response;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:
request returningResponse:&response error:&error];
NSMutableArray *greeting = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
for (NSDictionary *countryList in greeting) {
[myFinalListArray addObject:countryList[#"name"]];
}
*countryArray = [[NSMutableArray alloc]initWithArray:myFinalListArray copyItems:YES];
}
-(void)sendRequest
{
NSURL *url = [NSURL URLWithString:#"http://acumen-locdef.elasticbeanstalk.com/service/countries"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSMutableArray *myFinalListArray = [[NSMutableArray alloc] init];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError) {
if (data.length > 0 && connectionError == nil)
{
NSMutableArray *greeting = [NSJSONSerialization JSONObjectWithData:data
options:0
error:NULL];
if( !myFinalListArray )
{
myFinalListArray=[[NSMutableArray alloc]init];
}
for (NSDictionary *countryList in greeting) {
[myFinalListArray addObject:countryList[#"name"]];
}
}
[self printArray];
}];
}
//create method that will execute after response
-(void) printArray
{
NSLog(#"%#",myFinalListArray); //(This one showing all results..)
}
Use
__block NSMutableArray *myFinalListArray = [[NSMutableArray alloc] init];
This should work.
Happy Coding.
sendAsynchronousRequest runs asynchronously, meaning that the code below is already performed while the request is still running, so the NSLog is logging the empty array.
Only when the request finishes, the array is filled up but your outer NSLog was already performed.
I have a UITableView in a ViewController. The first time I run my application, table is empty. The table is filled, when I go through other views and I back to the view(which has a uitable). I use following solution, but it does not work. I put this part of code in ViewWillAppear and ViewDidLoad, but this is not working.
dispatch_async(dispatch_get_main_queue(), ^{
[self getthetablecontentfromserver];
[self.tableView reloadData]; // to reload selected cell
});
Anyone knows how can I populate the content of my table for the first time?
here it is the getthetablecontentfromserver method:
-(void)getthetablecontentfromserver{
NSURL *url = [NSURL URLWithString:#"http://exampledomain.com/data"];
config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:[NSOperationQueue mainQueue]];
[[session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
data= [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
[self.tableView reloadData];
}
] resume];
}
You should put your call to getthetablecontentfromserver on a background thread. Once that completes send a NSNotification back to the viewController to call reloadData.
I have a problem with my application.It freeze for several second when I tap the sidebar menu.
What happen when I tapped menu is I pass string that gonna be url for json data fetch in my mainviewcontroller.Then it freeze because I fetch the data and populating data in tableview.
However I really new to ios programming,I wonder how can I remove the freeze?.
thanks in advance
here is my code snippet for the mainviewcontroller:
Don't use dataWiyhContentsOfURL:, or at least not directly on the main thread. If you block the main thread then the whole app stops working (as you see).
You need to learn about background threads and callback blocks, and look at using NSURLSession to download your data and then process it.
Instead of using dataWithContentsOfURL (which will block the main thread and so the UI) you need to start an asynchronous connection. In the IF ELSE change the two requests to something like below. The completionHandler (Block) is executed when done, the data parsed, HUD removed and table Updated.
You can even (and in fact must) do this within your cellForRowAtIndexPath for each of the images, however, I would use SDWebImage as it has a cache and is very easy to use.
There are also other methods if this is not right for you such as NSURLSession.
Some other points;
I have also noted that the HUD is stopped on every iteration of the FOR and probably should be outside.
I also can not see how your data is being loaded so I added a [myTable reloadData];
I can not see that the "dictionary" object is needed as it can be added directly to the array (see code)
// If you have the status bar showing
// [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[HUD showUIBlockingIndicatorWithText:#"Please wait. . ."];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:kategori]];
[request setTimeoutInterval: 10.0];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue currentQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
// [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
if (data != nil && error == nil)
{
//All Worked
id jsonObjects = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
for (NSDictionary *dataDict in jsonObjects)
{
NSString *title_data = [dataDict objectForKey:#"title"];
NSString *thumbnail_data = [dataDict objectForKey:#"thumb"];
NSString *author_data = [dataDict objectForKey:#"creator"];
NSString *link_data = [dataDict objectForKey:#"link"];
[myObject addObject:[[NSDictionary alloc]initWithObjectsAndKeys:
title_data, title,
thumbnail_data, thumbnail,
author_data,author,
link_data,link,
nil]];
}
[HUD hideUIBlockingIndicator];
[myTableView reloadData];
}
else
{
// There was an error
}
}];
For the images something like (this is not tested). I am not sure what format your images are in but you should be able to just add it, this may need tweeking;
cell.imageView.frame = CGRectMake(0, 0, 80, 70);
__block UIImageView *cellImage = cell.imageView;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[tmpDict objectForKey:thumbnail]]];
[request setTimeoutInterval: 10.0];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue currentQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if (data != nil && error == nil)
{
//All Worked
cellImage.image = [[UIImage alloc]initWithData:data];
[cellImage layoutIfNeeded];
}
else
{
// There was an error
}
}];
You can start activity indicator and call fetch data method after few time...
- (void)viewDidLoad{
[activityIndicator startAnimating];
[self performSelector:#selector(fetchData) withObject:nil afterDelay:0.5];
}
- (void)fetchData{
Fetch your data over here
}
Or ideally you have to load data Asynchronous
For loading data Asynchronously check out the following link-
iphone-synchronous-and-asynchronous-json-parse
I Prefer MBProgressHUD.
Here is the link for 3rd Party API.
https://github.com/jdg/MBProgressHUD
Just copy these two files in your app.
MBProgressHUD.h
MBProgressHUD.m
I have an app that queries iTunes. When the data returns I want to segue from the search screen to a tableview that displays the songs.
My problem: I'm getting the data back but my segue is happening before the data is back so the numberOfRowsInSection count is still 0 and my app just stops. I don't understand why the conditional doesn't execute before my performselector method.
Here's my code:
- (IBAction)searchForTrack:(id)sender {
NSString *trackName = [[NSString alloc]init];
tracks = [[NSMutableArray alloc]init];
trackName = _searchText.text;
NSURLSession *session = [NSURLSession sharedSession];
NSString *appURL = [NSString stringWithFormat:#"https://itunes.apple.com/search?term=%#",trackName];
//NSLog(appURL);
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:appURL] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSMutableDictionary *jsonDict= [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
//NSLog(#"%#", jsonDict);
if (jsonDict)
{
for (id trackDict in [jsonDict objectForKey:#"results"]){
Track *track = [[Track alloc] initWithJSONDictionary:trackDict];
NSString *trackName = [trackDict objectForKey:#"trackName"];
NSString *artistName = [trackDict objectForKey:#"artistName"];
NSString *trackPrice = [trackDict objectForKey:#"trackPrice"];
NSString *releaseDate = [trackDict objectForKey:#"releaseDate"];
NSString *primaryGenreName = [trackDict objectForKey:#"primaryGenreName"];
[tracks addObject:track];
}
}
else
{
NSLog(#"No Darn Tracks Were Found or Something else is screwed up");
}
}];
[dataTask resume];
[self performSelector:#selector(switchToTableview)];
}
You don't show the full code, but the
}];
give's the clue that the conditional code is being executed in a block.
Your code is not executing in a one-line-at-a-time manner due to the block which is probably the cause of the problem, but you need to post all the code to confirm and to suggest the best solution (which will be remove the block, or add a completion part to the block).
I am fetching the data from a web service by synchronous method. I make the request to the web service then view freezes. I try to add the UIActivityIndicatorView before loading the data from the web service and stopped it after getting the data but activity indicator is not displayed.
I tried to put the web service data fetch operations on the different thread
[NSThread detachNewThreadSelector:#selector(fetchRequest) toTarget:self withObject:nil];
but at this time TableView crashes as it does not get the data for drawing the cells.
in fetchRequest function I am doing
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL
URLWithString:URLString]];
NSData *response = [NSURLConnection sendSynchronousRequest:request
returningResponse:nil error:nil];
NSError *jsonParsingError = nil;
NSDictionary *tableData = [NSJSONSerialization JSONObjectWithData:response
options:0
error:&jsonParsingError];
responseArray = [[NSMutableArray alloc]initWithArray:[tableData objectForKey:#"data"]];
for(int i = 0; i < responseArray.count; i++)
{
NSArray * tempArray = responseArray[i];
responseArray[i] = [tempArray mutableCopy];
}
This responseArray is used to fill the information in the cell
Please tell me how to do this. Any help will be appreciated ...
The problem lies in your very approach. Synchronous methods run on the main thread. And because the UI updates on the main thread, your app hangs.
So, the solution would be using an asynchronous method to download the data on a separate thread, so that your UI won't hang.
So, use the NSURLConnection's sendAsynchronousRequest. Here's some sample code :
NSURL *url = [NSURL URLWithString:#"YOUR_URL_HERE"];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
//this is called once the download or whatever completes. So you can choose to populate the TableView or stopping the IndicatorView from a method call to an asynchronous method to do so.
}];
You should better use Grand Central Dispatch to fetch the data like this so you dispatch it in a background queue and do not block the main thread which is also used for UI updates:
dispatch_queue_t myqueue = dispatch_queue_create("myqueue", NULL);
dispatch_async(myqueue, ^(void) {
[self fetchRequest];
dispatch_async(dispatch_get_main_queue(), ^{
// Update UI on main queue
[self.tableView reloadData];
});
});
Regarding the Activity indicator you can use in the start of the parsing:
[self.activityIndicator startAnimating];
self.activityIndicator.hidesWhenStopped = YES
And then when your table is filled with data:
[self.activityIndicator stopAnimating];