I have two ViewController and use a (tableview click) seque for opening the second ViewController.
My Problem is, the Second View Controller load much Data. So the time between switch is <> 10 Seconds. In this 10 Seconds the App freeze. Thats OK, but HOW can i insert a "Popup" or "Alert" Message like "Please Wait..." BEVOR . I have testing much tutorials for Popups and Alerts, but the Popup/Alter shows only, when the SecondView Controller is complete loaded. I will show the Message BEVOR the SecondViewController is compled loaded.
Example:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// IF i set here the ALERT, the Alter was only show, when the Second View Controller is complete loaded!
NSDictionary *rowVals = (NSDictionary *) [SearchNSMutableArray objectAtIndex:indexPath.row];
[self performSegueWithIdentifier:#"Foo" sender:self];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"Foo"]) {
// Get indexpath from Tableview;
NSIndexPath *indexPath = [self.SearchUITableView indexPathForSelectedRow];
// Get Data from Array;
NSDictionary *rowVals = (NSDictionary *) [self.SearchNSMutableArray objectAtIndex:indexPath.row];
// Destination View;
[MySecondViewController alloc];
MySecondViewController *MyView = (MySecondViewController *)segue.destinationViewController;
}
}
You are trying to fix the problem with the wrong solution. That solution is just as bad because the popup will also freeze for 10 seconds. What if you add more data and it takes 30 seconds or 10 minutes? Are you going to expect your users to see a dialog they can't dismiss for 10 minutes?
Are you fetching the data from the internet? If so you need to fetch your data asynchronously in the background.
If you're loading it from disk then there's too much being loaded that could possibly be displayed on one screen, you need to load only a small portion of it, and if that still takes a long time you need to load it asynchronously.
UPDATED -
You should have a model class for your application that is responsible for fetching the data from the internet.
Google Model View Controller to get some background information on what a Model is.
As soon as the app launches the model can start to download the data which needs to be down in the background (that's too big a topic to answer how to do that here).
The View controller can launch while the data is being downloaded and it can display a spinning activity indicator wheel or progress bar or dialog etc. while waiting. The important thing is the GUI will not freeze.
When the model has downloaded the data it needs to tell the view controller the data is now available, which it can do using NSNotification center.
There's lots for you to investigate and learn, to do it without GUI freezing it needs to be done properly, there's no shortcut, you have a lot to study.
#Martin,
i found a solution:
// Send the Request;
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
So the request are asynchrony. Thanks for your answer. Great +1
Related
I am developing a news application.I am using a table view to show the news. To download data from the server I am using sendAsynchronousRequest .
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if ([data length] > 0 && error == nil)
{
downloadedItem = [GNNewsItems saveDataToModel:data];
if ([self.delegate respondsToSelector:#selector(receivedResponse:)])
{
[self.delegate performSelectorOnMainThread:#selector(receivedResponse:) withObject:downloadedItem waitUntilDone:NO];
}
}
else if ([data length] == 0 && error == nil)
{
// Data not downloaded
}
else if (error != nil)
{
// error
}
}];
So far so good. Now consider a case:
User opens the app.
Table View send's a request to download the content of the first cell. Let us assume it takes 10 seconds to download the data.
User scrolls the table view to the 5th cell.
Table view sends the request for 5th cell.
Now user comes back to the first cell but the content of the initial request sent by cell 1 is not yet downloaded.
Table view will send a duplicate request for the first cell.
How can I cancel the duplicate request from the table view?
Create a NSMutableArray which will contain the indexPath of the cell for which request has already been initiated. Before initiating the web request for the a new in a cell check in the if request is already initiated or not. If not then initiate it else do nothing.
Your design description sounds like you are not using a standard Model/View design paradigm. You should store the headlines and other associated data, such as the full story or link to same in some kind of datastore, e.g. an array or CoreData etc. That object is what requests stories and updates data elements. Then it only does it once and in the background, and once gotten, it is done.
Then the tableView uses that datastore to populate table cells. It is a VERY bad design to be making over the air requests for stories each time a cell scrolls into view. It has the design issues you just mentioned plus it is very wasteful of users bandwidth and your server resources.
Basically I have a main screen that has a button on it, when you click on that button I want to load a list of users from a server and display them on the next screen in the tableview.
I can get the data with no issues, and pass it to my tableview with no issues - my problem lies with loading the data into the cells after I have received the data!
Processes exist like this:
Tap button
Starts NSURLConnection
Opens up UITableView on screen
Loads nothing
Data returns, adds to NSArray
Tableview Reload
In viewWillAppear - make local users NSArray equal received Data
Nothing loads.
If I then press back, then press the button again, all my cells are populated with the data I received before.
Thanks in advance for any help. I've been searching around for a while now :(
Edit:
When moving from main screen to the tableview
Note: getUsers sets up the NSURLConnection and starts the connection
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([segue.identifier isEqualToString:#"selectUser"]) {
DDNetworkRequest *networkRequest = [[DDNetworkRequest alloc] init];
[networkRequest getUsers];
}
}
Tableview class:
Note: returnUserList just returns the array of data which is set as a variable within the network class
-(void)viewWillAppear:(BOOL)animated{
DDNetworkRequest *networkRequest = [[DDNetworkRequest alloc] init];
users = [networkRequest returnUserList];
}
Network Class:
note: returnUsers manipulates and then saves the NSArray variable
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
NSDictionary *jsonDictionary;
jsonDictionary = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
[usersJSONDict isEqualToDictionary: jsonDictionary];
[self returnUsers:jsonDictionary];
DDUserMessage *userMessageTable = [[DDUserMessage alloc] init];
[userMessageTable.tableView reloadData];
}
** Edit2 **
returnUser method
-(void) returnUsers:(NSDictionary*)userDict{
userListArray = [userDict valueForKey:#"username"];
}
Without any code provided from your side, I guess u forgot this awesome operation inside the HTTP get request success block:
[self.tableView loadData];
M I RIGHT? ;)
After you receive the data, you have to pass it to the data source array, then call
[self.tableView reloadData];
Also make sure you call this line from the main thread. You cannot make changes to UI from another thread.
I went from using an NSURLConnection in my tableview controller to using an NSURLSession in a separate class with a callback that is processed in the tableview controller.
Now the data returned from my website does not get displayed in the populated table for up to 20 seconds even though it was loaded long ago.
I have an 'add' button on the navigation bar of tableview which brings up another view. When I click the add button, I can see the data in my main tableview is already populated as it animates to the next view. Returning back to the main view and the data is there.
I have tried implementing a number of ways to reload the data but they have no effect.
The old way which works fine, only lets me have one connection. I needed to have several connections available to call based on options I might have selected which is the reason for creating a new class to handle the connections and placing its callback in the tableview.
But this has created the problem of not being able to view the parsed return data immediately.
To me, it seems to be some type of threading issue, but I don't know how to troubleshoot it or how to correct it, so I am hoping someone here can suggest something to try.
Here is the applicable code in my new class we will call NetWorkClass for purposes of illustration...
#pragma mark - Get Parents
+ (void)requestParentsWithCompletionHandler:(RequestCompletionHandler)completionBlock {
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"insert url here"]];
[request setHTTPMethod:#"POST"];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
[[session dataTaskWithRequest:request completionHandler:completionBlock] resume];
}
This typdef is in the header file for my new class NetworkClass...
typedef void (^RequestCompletionHandler)(NSData *data, NSURLResponse *response, NSError *error);
Then in the main thread of my tableview controller class I call a method that invokes the Network class to retrieve JSON data from a website.
The following snippet is called from ViewDidLoad in my tableview controller...
// Use NSSesssion to request JSON data from my website [self getParents];
NSLog(#"getParents has completed!");
// The view however, will not display for approximately 20 or 30 seconds unless I click on the Add button which instantiates another view // and then I see the data in the tableview right away as it animates to the new view. NSLog(#"parentsTable:%#",parentsTable);
NSLog outputs the following:
2014-02-11 16:10:27.385 myApp[12667:70b] parentsTable:; layer = ; contentOffset: {0, 0}>
The data has not yet been processed by the callback at this point, but the website has been sent the request and the repsonse is being returned.
The callback is implemented in getParents...
-(void)getParents
{
[NetWorkClass requestParentsWithCompletionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
if (!error) {
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 200) {
if the response status code is 200, I propagate the data into the tableview and finish up by calling
[parentsTable reloadData];
All of the code following the statuscode check is identical to the code that works if I use an NSURLconnection (and its delegate methods) within the tableview to retrieve the data, so I have not included it here.
If I put a breakpoint at requestParentsWithCompletionHandler in getParents, and I single step from there, the first pass skips around my code. But if I then run from that point, it hits the breakpoint a second time and then falls thru to process the response, which is working as I would expect it since we have to wait a few milliseconds for the data to be obtained from the website.
The data has arrived within milliseconds, but it can take up to 20 seconds before the tableview fills in with data that was parsed if I do nothing.
Any help with this would be greatly appreciated.
You need to change your structure to the following:
In viewDidLoad You should call a method that will send an asynchronous request for the data. (strongly suggest using AFNetworking or restKit to do it instead of trying to get by with the code you are currently using)
On success you should populate an array with the data and [tableView reloadData]
You numberOfRowsInSection should look like the following:
If (!arrayContainingData) {
return 0; // For elegance, you can also return 1 and have a cell with an UIActivityMonitor
} else {
return arrayContainingData.count;
}
Finally use cellForRowAtIndexPath to build the cells.
Using Parse, after I'm logged in I am presented PFQueryTableViewController that displays a list of tasks and another detail view controller that allows me to edit the task detail and segue back. The issue right now is that the PFQueryTableViewController does not reflect the new changes after I finished editing and popping the task detail view off the stack. However the table view list does get updated when I go back to the login screen(view before the PFQueryTableViewController) and re-enter the table view again. I've tried the following:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.tableView reloadData];
}
and also
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self loadObjects];
[self.tableView reloadData];
}
Yet they don't seem to take effect. My guess is that the data is updated after the view is popped off and the table view appears. I'm just wondering if anyone has any insight on this while I'm investigating. Thanks!
You could try re-querying the queryForTable method in viewDidAppear (this would naturally use an API request on every view appearance however)
- (void)viewDidAppear:(BOOL)animated {
[self queryForTable];
}
This answer assumes that you are using Local Datastore and want to see changes made in it be reflected in a PFQueryTableViewController.
Because the ParseUI classes do not implement any form of caching though the local datastore, changes made in the detail view will not appear in the PFQueryTableViewController until the save operation has completed and the tableView has fetched the new items from Parse.
One solution to your problem would be adding a category to the PFQueryTableViewController that modifies how it fetches data to include what is in the Local Datastore as well.
You should make sure the data is saved before popping your view controller.
Use Parse's save method with completion handler.
[request saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
[self.navigationController popViewControllerAnimated:YES];
}
}];
You can use [self loadObjects] to trigger a refresh for all objects in the PFQueryTableViewController.
We're having a problem displaying a view above a tableView on iOS. Our approach
is to create a UIView that is a subview of a sublass of UIViewController, send
it to the back, and then bring it to the front upon didSelectRowAtIndexPath.
We're using an XIB to create the user interface. The view hierarchy is like
this:
View
-- UIView ("loading..." view)
-- -- UILabel ("loading...")
-- -- UIActivityIndicatorView
-- UITableView
-- UILabel
Here is what we're doing to try to display the "loading" view:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Create a request to the server based on the user's selection in the table view
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
NSError *err;
// Show the "loading..." message in front of all the other views.
[self.view bringViewToFront:self.loadingView];
[self.loadingWheel startAnimating];
// Make the request
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&err];
// Stop animating the activity indicator.
[loadingWheel stopAnimating];
// other stuff...
}
Whenever we leave the "loading" view at the front of all the other views in the
XIB, we can see that it looks as we want. However, when we leave the loading
view at the back (per the view hierarchy above) and then try to bring it to the
front, the view never displays. Printing out self.view.subviews shows that our
loading view is in fact in the view hierarchy. Interestingly, if we try to
change something else in our view within didSelectRowAtIndexPath (for example,
changing the background color of a label that's already displaying in the view),
the change never shows on the simulator.
The problem is the synchronous request. It blocks the main thread, and so the activity indicator does not get a chance to show.
A simple solution would be to asynchronously load the data on a global queue, and when everything is loaded, call back to the main queue.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Make the request
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&err];
dispatch_async(dispatch_get_main_queue(), ^{
// Stop animating the activity indicator.
[loadingWheel stopAnimating];
// other stuff...
});
});
While the solution above works, it blocks a global queue and so it is not ideal. Have a look at the asynchronous loading via NSURLConnection. It is explained in great detail in Apple's "URL Loading System Programming Guide".