The Background
I have an iOS application I am building with StoryBoard for the UI and AFNetworking for the back end. On my UIViewController I have some text boxes. When the user hits a submit UIButton I am making the AFNetworking call to a web service which returns an XML file.
The issue
I used the StoryBoard interface to hook up the button to a UITableViewController. Initially this worked fine because I had the code for the web service call inside the UITableViewController. The problem is that if the web service returns nothing I get a blank UITableView. So I moved the web service call into the UIViewController to be called when the button is pushed. OOPS! I make the web service call and the prepareForSegue gets called before the web service returns the data since its Asynchronous.
My Question
What is the best approach when getting data from a web service when you are using storyboards. Should I make the call inside the UITableViewController and then go back to the UIViewController if there is no data or an error returned from the service. If so how do I get back. I have tried self.navigationController popViewController but it comes up with a warning in the console that removing a view controller can mess up the stack.
Is there a way that I can control when the segue is called and only do it once I have data back from the web service?
Posting this step by step answer to help anyone else out that may need help with this:
Connect the two ViewControllers in Storyboard which will create a segue.
You simply select the first view controller (make sure the viewController is highlighted with a blue line and you didn't select like an image view or navigationItem. Then CTRL and click on the destination view controller (where you want to transition to).
The round circle in the middle is called the segue. If you click on that and go to your attributes panel on the right, you will see this:
In the identifier enter a name you want to call this transition: for example myNewPage.
Whenever you are ready to make the call to the next scene. Use this delegate method to do pass data from one controller to another or do anything special:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NameOfDestinationController *dataViewController;
dataViewController = segue.destinationViewController;
if ([segue.identifier isEqualToString:#"nameOfYourSegueIdentifier"]) {
dataViewController.variable1 = self.variable1;
...
}
}
Then in my case I needed to trigger the segue after I got a success from the AFNetworking web service call. So in the Success block I added this line:
[self performSegueWithIdentifier:#"nameOfYourSegueIdentifier" sender:self];
It works perfectly now!!!
Related
I have two view controller A & B.On ViewController A i am displaying data from server.I have nav button Update to go to ViewController B.On B i can update the data which was shown on ViewController B.Now when data is updated on server i show an alert 'Update Success' on click of Ok i go to previous ViewController.But i don't see the updated data because i removed the ViewController B from stack but A is not reloded.
Code for going back to ViewController A
[self.navigationController popViewControllerAnimated:YES];
I assume you are sending a web call from viewDidLoad(). So in order to update your viewController A Add your web call method in viewWillAppear()
The state of your "data" on view controller A has not changed and you need to refresh it. If you're fetching data for view controller A within -viewDidLoad, try moving it to a different life cycle method like -viewWillAppear.
Remember, -viewDidLoad gets called only when the view controller loads which happens once, however -viewWillAppear gets called before it appears which will happen often if you're hopping back and forth between views.
You have to call your API method in viewWillAppear(). So, whenever you come back from any viewController, your updated server data will reload itself.
I am currently loading a new view dynamically by evaluating the segue.identifier in the prepareForSegue:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString: #"DetailViewSegue"]) {
DetailViewController *detailController = segue.destinationViewController;
// ... do something with the controller
}
}
Some of those views need a bit of time because gathering the data from the Internet and displaying that in a table scheme needs a rather long time.
Therefore I want to display a Progress HUD like KVNProgress, however the main issue now is that the Progress bar shows up too late, right before the new view is ready. As far as I have seen is the main Problem, that the prepareForSegue method and therefore the KVNProgress was called immediately before loading all the data, but loading a new view seems to be preferred instead.
Another thing I tried was to call KVNProgress within a IBAction or didSelectRowAtIndexPath and to call the performSegueWithIdentifier within his own thread. However this is (as the console output suggests) discouraged, and does not really work either.
Thanks!
EDIT:
There were multiple attempts achieving this, currently I am trying to do it that way:
(IBAction)buttonPushed:(id)sender {
[KVNProgress showWithStatus:#"Loading"];
[self performSegueWithIdentifier:#"DetailViewSegue" sender:sender];
}
As I've described, i think this is the earliest I can display the ProgressHUD before the new action is loaded. However it seems that the current view is blocked while the new one is loaded, and therefore the hud is not shown.
There are two solutions for this.
When the user taps on a "Load data" button,
you display the HUD (and stay on the current screen)
load the datafrom the internet (typically on a background thread)
when it's complete, you call performSegueWithIdentifier to move to the DetailView screen
Alternatively, you might prefer to do this:
you call performSegueWithIdentifier as soon as the user taps the
button
you move to the new DetailView screen
in the DetailViewController.m, you display the HUD and call the
background thread to load the internet data
There's pros and cons to both (you might find the latter is more maintainable, as other screens call this DetailView screen and also need to load the internet data to populate this screen).
The key is to decide which way you want to implement this, then get that particular ViewController to do both parts, handling the HUD and loading the internet data.
I try to explain my problem. In appdelegate I have to select a rootViewController depending on the result of an asynchronous request (I'm using AFNetworking framework). In fact, I need to know if my user is profiled or not: if he is profiled I can show him app's Home, if he is not I have to show him a profilation view.
In storyboard I set the Home view as the designated entry point, but in this way this view is always shown until the asynchronous request is completed. Is there a way to make appdelegate wait for the response?
I think there is't good solution to let app delegate wait for the response because if the network connection will be poor the app loading time will be very long and OS could kill your app or user can turn it off.
You can add some loading view controller (with animation so user will know that the app is doing something) instead of home one and when you receive the response present appropriate view to the user (modal segue could do the job).
Hope this help
A better solution is to use splash screens. That is when your app gets loaded in AppDelegate, create and push a splash view controller. Which would just contain a single UIImageView covering whole screen showing your application splash image. Upon asynchronous call completion, pop that splash view controller and push your required view Controller.
Many apps use this way to download necessary asynchronous data before showing the app. So that user don't see empty screens or garbage data.
If something gets failed like internet connectivity failure or server response error, etc., Show error to user and perform error handling according to your app logic.
You can programatically navigate to the root view controller as
[self.navigationController popToRootViewControllerAnimated:YES];
This code can be put in the condition of result.
Or in your way, I think you are created a segue for navigation to the rootViewController. You can programatically perform a segue using
- (void)performSegueWithIdentifier:(NSString *)identifier sender:(id)sender
If you are using the AFNetworking, just add a method in the success block and pass the response to that method in a parameter of dictionary. Check your response in the method and choose the controller which you want to make make the root view controller from that method.
I have an app created from Xcode 4.5's boilerplate Master-Detail Application with CoreData, StoryBoards and ARC turned-on that is not calling prepareForSegue. CoreData isn't currently being used, but it will be to cache XML responses. performSegueWithIdentifier works, but prepareForSegue isn't and I'm having troubles passing/access data from the MasterViewController to/in the detailViewController created by performSegueWithIdentifier.
My basic set-up is similar to what's discussed in the thread: Storyboards and UISplitViewControllers. There's an image of a storyboard on page three that's very similar to my set-up (I don't have enough rep to post images).
In a nutshell:
I create a standard splitView arrangement
The MasterViewController builds the main table
Each cell corresponds to a URL that returns XML data that determines what's in the detailView
The fetched XML is parsed using a NSXMLParser operation/class
The fetched XML determines which detail view is needed and the MasterViewController calls the appropriate 'replace' segue (via performSegueWithIdentifier) to kick-off the corresponding detailViewController to display the fetched XML
The problem that I'm having is that prepareForSegue isn't being called, so I can't pass the fetched XML to the detailViewController.
What I need is one of the following:
prepareForSegue to be executed
a way to know segue.destinationViewController inside handleLoadedResponse:notif
a way to get access to the "currentResponse" variable in the MasterViewController from inside the viewDidLoad method of the detailViewControllers
a way to find the MasterViewController via its StoryBoardID
What I've tried:
Putting NSLog() statements in each viewController's prepareForSegue -- none of them are called
Digging through the self.parentViewController chain in the detailViewController to find the MasterViewController that called performSegueWithIdentifier -- I can't find the class variable I'm looking for
Read pretty much every "prepareForSegue not called" post I could find -- They all seem due to some coding/storyboard error that I don't see in my code/storyboard
Could the problem be that I'm calling:
[self.navigationController performSegueWithIdentifier:#"theDesiredSegue" sender:self];
from inside the handleLoadedResponse:notif call-back and the app is trying to call prepareForSegue in my parsing object?
TIA
Ray
Well, chalk this up to staring at the code too long and/or newbie-ness.
After triple-checking everything that I though was obvious, I thought the only thing left was that the segue code didn't behave correctly inside the NSXMLParser call-back, so I switched the handleLoadedResponse:notif routine to store the parsed data and send-out notifications.
While debugging those changes, I realized that my segues were attached to the wrong object. They were actually attached to the navigationController and not my viewController. Visually, things looked like the story board on page three of RWForums: Storyboards and UISplitViewControllers, but they really weren't. The end result being that even though it seemed like my code was calling performSegueWithIdentifier and that it's prepareForSegue method should have been called, it was actually calling the navigationController's segue, so the navigationController's inherited prepareForSegue was being called.
Recommendation: Make sure you re-re-re-check all the connections in the StoryBoard Editor with the Document Outline open.
Ray
Can someone more knowledgeable than I explain performSegueWithIdentifier:sender: for me? I need to switch views (and classes) and also carry a few NSStrings and IDs over to that view's class. I was wondering if this is possible with performSegueWithIdentifier:sender:
Thanks!
First, you have to have set up the segue in your storyboard and give it the appropriate identifier. (Click on the segue (left panel) and then click on Attributes (right panel).
You can then link this to buttons or selection of table rows from your storyboard, or you can call it in code using performSegueWithIdentifier:sender:.
After this, your view controller will be sent the prepareForSegue:sender: message. You override this method in your view controller subclass, and can configure the target view controller as follows:
TargetViewController *targetVC = (TargetViewController*)segue.destinationViewController;
targetVC.string1 = string1;
And so forth. The sender in this method will be the object that you use as the sender in the original method call.
Most segues are initiated automatically as the result of some user interaction. For instance, if you have a segue that is wired up from a button to a scene in a storyboard, when the button is tapped the segue will automatically initiate.
Occasionally, it makes sense to trigger a segue programmatically - e.g. you have a High Scores scene that is displayed when the user wins a round of a game. There's no way to express the concept of winning in the storyboard itself, so you can instead create a segue, assign an identifier to it, and invoke -performSegueWithIdentifier:sender: at runtime.
The other segue related method on UIViewController, -prepareForSegue:sender:, is the method you should override to perform any customization on the destination view controller.
In prepareForSegue:sender: you get a chance to configure the destinationViewController: that's where you'd pass it the data it needs. It's discussed in Cocoa Application Competencies for iOS.
Today I ran into the issue of performSegueWithIdentifier: not executing due to the fact of not having set a delegate queue on my URL session.
So by any chance, check if you are actually setting a delegate queue when creating your URLSession, else URLSession will create it's own.
urlSession = [NSURLSession sessionWithConfiguration:sessionConfigObject
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
I mention this here because I quite often see URLSession handling ending up calling some sort of UI related activity. And performSegue needs to be executed on main, or else it will do just nothing.