IOS : passing data between viewController (destination embedded in a NavigationController) - ios

I'm just learning IOS development, I would like to pass data between ViewController. I'm using Storyboard and I'm using the "prepareForSegue" method.
My main question is about the pattern I found in many forums and blogs about this "transmission" of information. When the origin controller needs to pass data to a destination controller, the origin controller access the destination controller using the code :
[segue destinationViewController]
This is fine, the origin controller doesn't need to know exactly the destination controller details (I'm using protocols).
But when the destination controller is a NavigationController (a ViewController embedded in a NavigationController), it seems that the recommended practice is :
[[segue destinationViewController] topViewController]
But if I do that, it means that the origin controller must know that the destination controller IS a NavigationController. I would like to avoid that if possible ?
Maybe I'm doing something wrong ? Is there another way to do it ?
The origin controller is a "detail page" (coming from a TableView), the destination controller is the "edit page".
Any help is welcome.
Thanks

UINavigationController *navTmp = segue.destinationViewController;
YourController * xx = ((YourController *)[navTmp topViewController]);
xx.param = value;

I see two possibilities:
Use -isKindOfClass and check if it's a UINavigationController
Create a protocol whith a -rootViewController method, create two categories conforming the protocol, one on UIViewController, another on UINavigationController, implement both, the one for UIViewController should return self, the one for UINavigationController should return topViewController. Now you'll be able to drop those categories on any controller, and use [[segue destinationViewController] rootViewController]

Check to see if the destinationViewController is a UINavigationController, and if it is, then get its topViewController. That way it just automatically handles either case, and it's safe.

But if I do that, it means that the origin controller must know that
the destination controller IS a NavigationController. I would like to
avoid that if possible ?
One possible solution is to subclass UINavigationController such that it can accept whatever data your source controller provides and in turn passes that data on to its root view controller. That might make particular sense if you have a number of segues, some leading to nav controllers and some not, and you want to handle them all the same way.
In other words, create a UINavigationController subclass that acts like a proxy for its root controller.

Related

One segue to rule them all

I have 4 ViewControllers in my storyboard. I want all of them to be able to access my "Settings" ViewController by performSegue.
Is it possible to have ONE segue to perform this, instead of ctrl + drag from each and every ViewController to my "Settings" ViewController?
No its not possible with a single segue. You need 4 different segues from 4 different ViewControllers. But you can do this programatically.
Make an extension for UIVIewController
extension UIViewController
{
func showSettingsScreen()
{
let storyBoard = UIStoryboard(name: "YourStoryBoardName", bundle:nil)
let settingsScreen = storyBoard.instantiateViewControllerWithIdentifier("YourSettingsViewControllerID")
self.navigationController?.pushViewController(settingsScreen, animated: true)
}
}
Now you can call showSettingsScreen() from any of your view controllers(Make sure this view controller has a navigation controller).
You cannot do that. A segue has only one source and one destination. You could programatically instantiate your Settings ViewController and display it either by using push or by using present. What you should think of though is why do you have to go to settings from so many places. It might be a sign of bad design and duplicate code. Usually applications have only one such button/action that can be accessed from multiple screens (by using some kind of container view implementation) or from only one screen.
I really dont think so there is a way to do so. U ought to connect ur SettingsViewController to all of your 4 View Controllers, add segue , and define a segue identifier which is used in
prepareForSegue:sender:
or
shouldPerformSegueWithIdentifier:sender:
methods. U can access segues through these identifiers. If u find "ctrl + drag from each and every ViewController to "Settings" ViewController " tasky you can opt for Navigation Controller as well. U just have to embed Navigation Controller in your storyboard and define Storyboard Id for every View Controller and you are done. Just use storyboard id of view controller to be instantiated and u good to go.
ViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"ViewController"];
[self.navigationController pushViewController:vc animated:YES];
Apart for assigning storyboard id you dont have to worry about storyboard ,No ctrl+drag thing.
I thought of one more elegant solution i.e. using Container View. You can take button to switch, SettingsViewController as common, in your Container View Controller while displaying every ViewController.
happy Coding..
There is one way to do this. Create a root view controller and matching view which contains a single embedded view. Add your segue to this root controller. Then in your app you switch in the other view controllers using standard child container techniques. This is pretty much the concept that UINavigationControllers use.
One advantage from this is that if you want to have common elements which are visible to all controllers then you can add them to your root controller.
But it all depends on what you are trying to achieve.

how to pass a variable back to root navigation controller from the UIViewController using Protocol and Delegate in Swift

Xcode Image
As you can see in the image attached I have the root navigation controller called - Notifications and a UIViewController called NotificationsController.
So my Question is how can i pass a variable from NotificationsController back to Notifications using Protocol and Delegate, because in this case there is no segue but a default relationship between them.
Is my question correct or is there another way to do what i need.
Any help is really appreciated
To reference the navigation controller from the view controller (it is an optional so you need to handle that):
// self is a UIViewController
self.navigationController
To reference the view controller from the navigation controller:
// self is a UINavigationController
let index = // Index of the view controller. You may need to iterate over viewControllers to find this.
self.viewControllers[index]
You don't really need to set up a delegate. As keithbhunter pointed out in his answer, a view controller has a navigationController property that will point to the navigation controller that manages it.
I suggest you define a protocol for the messages you want to send to your navigation controller, and then have your custom subclass of UINavigationController conform to that protocol.
Within the view controllers that are on the navigation controller's stack you can fetch a pointer to your navigation controller and cast it to type UINavigationController<myNavControllerProtocol>.
(UINavigationController that conforms to myNavControllerProtocol. I'm working in Objective-C these days and don't remember the exact syntax for that.)

displaying a ViewController from a non UI class

I perform some data loading tasks from an Ojective§C class and once everything is loaded, I simply wants to display a Viewcontroller subclass prepared in a storyboard.
So when everything is ok, the following method is called:
- (void)loadingNextView
{
CABBndGSite *mySite = [CABBndGSite alloc];
CABBndGSelectLanguageViewController *vc = [[mySite myRootViewController].storyboard instantiateViewControllerWithIdentifier:#"SelectLanguageViewController"];
[[mySite myRootViewController] presentViewController:vc animated:YES completion:nil];
}
So I verified that myRootViewController is not nil. It's a UINavigationController class.
vc is not nil so it found my view in the storyboard.
Anyway, the presentViewcontroller message seems to doing what expected.
Certainly a stupid mistake but my poor iOS programming knowledge lets me in the fog!
I use this code from ViewController subclasses with success and as here I get a valid ViewController pointer, I don't understand why it doesn't work.
I also tried to implement the AppDelegate method explained here How to launch a ViewController from a Non ViewController class? but I get a nil navigation pointer. Maybe something not well connected in my application
May I have some explanation?
Kind regards,
UINavigationController maintains a stack of view controllers. You can access this stack through the viewControllers property. To present your view controller, you can:
(a) have the navigation controller push the new view controller on to
the stack (pushViewController:animated:);
(b) have the top view controller in the view controller stack present
the new view controller modally (presentViewController:animated:completion:), or;
(c) add the new view controller to the view controller stack array
manually by assigning a new viewControllers array to the navigation
controller's viewControllers property (setViewControllers:).

How to reference and declare a UINavigationController's custom method

At the moment, I'm trying to call an existing UINavigationController's method from a scene.
My storyboard looks like:
->UINavigationController--(rootViewController)-->ViewController-->Scene
I need help understanding how to call the existing UINavigationController's custom method named showGameCenterMenu() from the scene. This is what I have so far:
UINavigationController *navRef = self.view.window.rootViewController.navigationController;
[navController performSelector:#selector(showGameCenterMenu)];
I understand that my method call is probably incorrect, but any help would be appreciated understanding this and whether or not I'm breaking best practice by directly referencing this...
If the navigation controller is the root view controller then you are navigating too far (so you probably get nil), you should have:
UINavigationController *navRef = (UINavigationController *)self.view.window.rootViewController;
(with a cast from UIViewController)
Cast the pointer you're getting to your custom navigation controller class and then call its method.
With the help of the great guys in the comments I was able to find a suitable solution..
(NavigationViewController is my custom UINavigationController class);
NavigationViewController *navRef = (NavigationViewController *)self.view.window.rootViewController;
[navRef showGameCenterMenu];
This allowed me to send a method call to my NavigationViewController which displays the Game Center Menu one Layer above the existing View Controller by presenting it with .topViewController appended. Thanks again guys :)

iOS: Sharing state/properties between 3 controllers

I have a form and it gives users an advance mode. I've already googled and looked around at different SO questions (sharing data between controllers, protocols, and passing data between segues) but I'm wondering if there's a better way.
Is there a way for me to have some sort of "master controller" that holds all the data while going back and forth between 3 different controllers?
If I can just hold the data for the second controller and allow my user to make that quick advance edit in the third while keeping it's data intact, that'll do for now.
Thanks in advance
Here's a quick walkthrough of my app:
FirstViewController: User selects an option
SecondViewController: User does some editing while storing that option
(*Optional)ThirdViewControl: User one more quick edit using a web view
*xcode5/iOS7
If you are passing data from one view controller to the next and you are using segues, then call a method on the next view controller in line from prepareForSegue. For example, when segueing between ViewController1 and ViewController2, add this code to ViewController1 and repeat as necessary in other view controllers:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
ViewController2 *viewController = segue.destinationViewController;
[viewController configureWithSomeState:self.someState];
}
This code assumes that someState is a property defined on ViewController1.
EDIT: One thing you could do--although I do not like this approach because it forces your VCs to have special knowledge about their parent controller--is to derive UINavigationController, then in your storyboard, use the new class for your navigation controller. Store the state in the derived navigation controller and access it from each VC like this:
DerivedNavigationController *navigationController = (DerivedNavigationController *)self.navigationController;
navigationController.someState...;
Typically I'd put information that is used throughout the application in the application delegate where everybody has access to it.
Another possibility is to implement a singleton data management class to hold it for you.
In this case it seems like the application is really a pretty linear flow, so I'd just pass the selection from vc1 to vc2 and then again to vc3 in the respective prepareForSegue calls.

Resources