Stop Navigation Controller from affecting other View Controllers - ios

I have an app that uses a Navigation Controller with three View Controllers for step-by-step user setup. So the first View would be Step 1, second Step 2, and etc. And all this would be embedded in a Navigation Controller so the user would be able to go back and forth. However, once this setup is done and the user presses the 'Done' button, the app programmatically moves to another section of the storyboard which is supposed to be separate from the initial setup. I programmatically segue to this section with the following code:
let vc : AnyObject! = self.storyboard!.instantiateViewControllerWithIdentifier("welcomeViewController")
self.showViewController(vc as! UIViewController, sender: vc)
where "welcomeViewController" is the identifier for the post-setup ViewController. However, once this happens, the Navigation Controller Bar still appears on the top and you can still navigate back into the setup menu, which is not at all what I want. Here's what I mean:
The user shouldn't be able to access the initial setup after already setting up. My storyboard looks like this:
The left four boxes are for setup. The right three boxes is the 'Main Menu' I want to create.
As you can see, there's no arrow between the last setup Controller and the Tab Bar Controller because I performed that segue programmatically (as shown above). How do I get the Navigation Controller to stop affecting the rest of my View Controllers?

This happens because a segue under the hood makes use of the current UINavigationController, so it doesn't matter if you perform the segue programmatically or in the storyboards directly.
There a number of ways how to approach this. A very easy way to achieve it is to simply hide the back button in your WelcomeViewController using self.navigationItem.hidesBackButton = YES;.
However, this doesn't really solve your problem if you really want a separation between these view controllers and want to get rid of the UINavigationController that was responsible for your setup process. What I would actually recommend for that kind of situation is to use two different storyboards, one for your initial setup and one for the main part. Then you switch your UIWindow's rootViewController to a view controller from the other storyboard programmatically. This can be done in few lines of code and helps to separate your concerns and keep a better visual overview on your app's flow.
Let me know in the comments if you need further explanation :)
Switching storyboards can be achieved using the following code:
UIStoryboard *main = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
UIViewController *welcomeViewController = [main instantiateViewControllerWithIdentifier:#"WelcomeViewController"];
UIWindow *mainApplicationWindow = [[[UIApplication sharedApplication] delegate] window];
mainApplicationWindow.rootViewController = welcomeViewController;
But if you want to instantiate the whole UITabBarController you use in you r main interface, you'll have to give it an ID (e.g. MainTabBarController) in storyboards so that you can instantiate it using instantiateViewControllerWithIdentifier:. So then, instead of instantiating your WelcomeViewController, you'll go:
UIStoryboard *main = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
UITabBarController *mainTabBarController = [main instantiateViewControllerWithIdentifier:#"MainTabBarController"];
UIWindow *mainApplicationWindow = [[[UIApplication sharedApplication] delegate] window];
mainApplicationWindow.rootViewController = mainTabBarController;

A very easy solution is to this problem, is to make a segue from your last UIVieController you want to be embedded in the UINavigationController to the one you want out of the UINavigationController.
Step 1.
Ctrl drag from the yellow icon on top of the UIViewController (embedded in the UINavigationController) to the UIViewController you want to segue to (out of the UINavigationController), and select "Present Modally"
Step 2.
Give the segue an identifier in storyboard
Step 3.
In your code, where you want to do the segue, put;
performSegue(withIdentifier: "yourSegueID", sender: self)
This will do the trick, and makes your storyboard more readable.

Related

Creating a central log out view controller in storyboard

I am working with Parse, and one thing I have implemented in my app is their built in PFLogInViewController. This controller will be presented at two times in the application - when the app first starts and the user is not logged in, and when the user taps the "Log out" button of my application (logging out takes them back to the PFLogInViewController, as you are required to sign in to use the app). I would like to set this up using Storyboard, as that is how the rest of my app is laid out. How could I set up a central view controller (a PFLogInViewController) that is accessed at these two times? I have already Subclassed PFLogInViewController and set it up, I just need advice on how to place it in Storyboard and how to connect it to my views. To make this question help as many people as possible, the general theme of my question is how does one establish a central Login/ViewController that can be accessed at different points in the application using Storyboard. Attached is the basic idea of what I'm trying to accomplish. I haven't been able to successfully segue to the initial TabBarController, and I'm not sure how I should make the LoginController the initial ViewController if I can't segue. I am programming in Swift, if it matters.
There are a few ways to do this depending upon your application. One way is drop a UIViewController onto the storyboard, but don't wire it up to anything (no segue). Create a storyboard id for it such as "MyLoginVC". Do the necessary subclassing of UIViewController and attach the class to your VC. Then, when you want to display the VC simply do the following or wire this up to your logout button
id destinationVC = [self.storyboard instantiateViewControllerWithIdentifier:#"MyLoginVC"];
[self.navigationController pushViewController:destinationVC animated:YES];
In addition, if you want to show the login VC as the very first VC when you launch your app, then perhaps in your AppDelegate
// Load Root view controller
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
self.rootVC = [storyboard instantiateInitialViewController];
self.window.rootViewController = _rootVC;
[self.window makeKeyAndVisible];
// Load Login view controller
id initialVC = [storyboard instantiateViewControllerWithIdentifier:#"MyLoginVC"];
[initialVC setModalPresentationStyle:UIModalPresentationFullScreen];
[_rootVC presentModalViewController:initialVC animated:NO];
When you finish with your login VC (i.e. successful login) then within login VC
[self dismissViewControllerAnimated:NO completion:nil];
and alternatively instantiate your first VC with something similar to the following from within login VC. Note, since you loaded the root VC above first, it is already there with the login VC sitting over it. When you dismiss login VC, the underlying root VC should be ready to rock and roll. Otherwise you can do the following:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
RootTabBarController *tbController = (RootTabBarController *)[self.storyboard instantiateViewControllerWithIdentifier:#"rootTabBarController"];
[self presentViewController:tbController animated:YES completion:NULL];
}
I think what you want is an unwind segue. Here are the instructions I follow for an unwind segue: https://github.com/bradley/iOSUnwindSegueProgramatically
If the link dies, here is what it said:
In your storyboard create two view controllers.
Subclass UIViewController twice, once for each of the view controllers in your storyboard.
Connect these view controllers to the view controllers in your storyboard.
Make a segue between the first view controller and the second by control+dragging from the first to the second.
Click on the segue you created and give it an identifier in the attributes inspector.
Make a button on the first view controller and link it to an IBAction in its UIViewController subclass.
When this button is pressed, the second storyboard should appear. To make this happen (we are doing it programatically) put the following into the implementation of the action you just created:
[self performSegueWithIdentifier:#"nameOfTheSegueBetweenOneAndTwo" sender:self];
Create a second method in the implemention of the first view controller with the following:
- (IBAction)returnToStepOne:(UIStoryboardSegue *)segue {
NSLog(#"And now we are back.");
}
This method will work to unwind any view controller back to this view controller. Notice that we implement the method in the view controller we wish to return to.
Go back to the storyboard. Focus in on the second view controller. If it is active, you should see a dark bar beneath it with 3 symbols on it. One of these is orange and when hovered over will show the name of the UIViewController subclass that this view controller represents. Control drag from this symbol woth the green symbol that means 'Exit'. You should see all available segue unwinds, which XCode automatically enumerates when you create segue unwind implementations inside UIViewController subclasses that you have shown on your stroryboard. Hence, you should see the segue 'returnToStepOne' as an option. Select it.
In your storyboard's document outline, find the section for the second view controller. You should see an item listed below it with a grey symbol that says something like "Unwind segue from ... to Exit." Click on this item.
Important and easily missed step follows!
On the right side of your storyboard, in the attributes inspector, you should see two fields. One for 'Identifier' and one for 'Action'. In most cases, the 'Action' field will have the text 'returnToStepOne:', which is what we want, but the 'Identifier' field will be blank. Fill this field with the text: 'returnToStepOne' (note that we leave out the colon).
Create a button on the second view controller and link it to an IBAction in its UIViewController subclass.
In the implementation for the method you just created, put the following code:
[self performSegueWithIdentifier:#"returnToStepOne" sender:self];
Run the application. You should now be able to unwind from the second view controller to the first.

ECSlidingViewController with Push and Unwind Segue

I am using ECSlidingViewController with storyboards. ECSlidingVC is my root (starting) controller. My left menu is a TableView with static cells, and my TopViewController is a navigation controller. I want to have a single NavigationController for all my app.
From my left menu i cant use push or unwind segues, i understand that part though. i can only use ECSlidingSegue which changes topviewController of ECSlidingVC and which destroys my navigation controller and it's stack.
i want to be able to go back from a menu item VC to previous VC in my main nav controller. lets say basically i want ECSlidingVC to not change topViewController but push destination viewController to my source.topViewController.navigationController.
Also i need to use unwind segues with my menu items. i need to go back to a VC in my main nav controller.
i inspected ECSlidingSegue source code and all it does is to replace topViewController.
is there a built in method (or segue) in ECSlidingViewController for pushing (or unwinding) VC into source.topViewController.navController or do i need to implement a custom segue myself?
I think the best way to go is for you to implement a custom segue yourself. Something like ECSlidingNavigationSegue, which would look for your topViewController, then check whether it's a UINavigationController and then push the destinationController to it.
It's basically the same perform method as the ECSlidingSegue, but with this feature of pushing a controller to the topViewController instead of replacing it.
Good luck!
In case someone haven't found answer, I did it in this way.
1- #import "UIViewController+ECSlidingViewController.h" to your menuViewController
2- Set stroboardID of your destinationViewController to "someID"
3- When triggering some action, in backend, use this code:
if(self.slidingViewController.currentTopViewPosition == ECSlidingViewControllerTopViewPositionCentered){
[self.slidingViewController anchorTopViewToRightAnimated:YES];
}
else{
self.slidingViewController.topViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"someID"];
[self.slidingViewController resetTopViewAnimated:YES];
}

Storyboards - how to perform segues automatically without animation and showing the screen

I have an application where the user has to enter some information on the initial screens, such as the login credentials. All the navigations are done using a storyboard and segues, which automatically sets the navigation bar on top.
I have two storyboards that share the same controllers, so I use the same name for the segues (ipad and iphone versions).
So when the user comes back to the application, I read the core data and know that he has already performed the initial steps, so I would like to "skip" those screens.
Problem:
I can only execute the segues after the view is visible, otherwise the navigation is screwed up. But when doing so, the user sees the screen briefly and sees the animation "pushing" that screen away. I'd like to keep the navigation history on the navigation bar, that is why I want to use the segues and all the logic associated with them.
All the solutions point to creating the views programatically and putting them on the stack, but I'd like to take advantage of the storyboards.
Here is one way of doing it;
In your StoryBoard, assign an identifier to the second view controller (this is done on the identity inspector, setting the Storyboard ID field). In the code below, i have named mine secondVC;
Then in the viewDidLoad for your first controller (the one you want to skip but come back to) do something like this;
- (void)viewDidLoad{
[super viewDidLoad];
/// validate viewController one being displayed
if(dontDisplayFirstController){
UIStoryboard *storyBoard = self.storyboard;
UIViewController *targetViewController = [storyBoard instantiateViewControllerWithIdentifier:#"secondVC"];
UINavigationController *navController = self.navigationController;
if (navController) {
[navController pushViewController:targetViewController animated:NO];
}
}
}
This will efectivly push to the second viewController whilst still maintaining viewController one in the navigation tree.

Which is the recommended way to segue from one view controller to another?

I like to segue from the current view controller to the "settings" view controller. Which method is more efficient to transition and why? Thanks! I have to segue in code because I have to observe a condition at run time.
Method 1:
UINavigationController *navigationController = (UINavigationController *)[[[UIApplication sharedApplication] delegate] window].rootViewController;
SettingsViewController *v = [self.storyboard instantiateViewControllerWithIdentifier:#"settings"];
[navigationController pushViewController:v animated:YES];
Method 2: In the storyboard, control-drag the current view controller icon (bottom left) to "settings" view controller and then name the segue identifier "gotoSettingsVC", set style to "push" and then use this code...
[self performSegueWithIdentifier:#"gotoSettingsVC" sender:nil];
Both work just fine.
If you're using storyboards, segues are probably the better way to go. Instantiate the initial view controller and let the segues be your guide.
Also, while Method 1 can be used with storyboards, unlike segues, it also can be used with regular xibs (or no interface builder at all) as well.
Other than that, it comes down to preference. Some people hate the interface builder and others swear by it. ;)

Moving from one view to another iOS

I'm relatively new to iOS development. I am to move from one viewController to another I use a modal segue transition on button click. This is a game so i want to allow the user to click images to essential move the the app menus.
I have a main page that displays several images, on clicking one i want to be able to move to another view. Currently doing this with a modal segue is causing odd problems with my touchesEnded event where if, for example, i navigate to a page 3 times the touchesEnded event is fired 3 times.
Is there a better way for me to do this or am i just missing thing fundamental?
Thanks
Yes, I think you must make the the navigation controller your root view controller then push views accordingly
UINavigationController * nav = [[UINavigationController alloc] initWithRootViewController:YOUR_BASE_CONTROLLER]
self.rootViewController = nav;
this is in your app delegate.
Then in your action method
[self.navigationController pushViewController:secondViewController animated:YES]
Im assuming you are using the Storyboard to link VCs using segues.
Modal segues are great for simple transitions but really seem to limit what you can accomplish when they are just linked through SB. Ive found that creating an IBAction that includes the following for a VC segue will allow you to not only control your segues more efficiently but also allow you to have a clearer view of what is actually occurring during the transition.
-(IBAction)goToVc:(id)sender{
//Other code to take place during the segue here
//This will identify the Storyboard in use
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
//This will identify the View Controller to switch to
SecondViewController *vc2 = [storyboard instantiateViewControllerWithIdentifier:#"SecondViewControllerID" ];
[self presentViewController:vc2 animated:YES completion:NULL];
}
Every time you perform a modal segue, you lose the UINavigationController that you were previously using. What you need to do is embed a UINavigationController in the view that you are performing a modal segue to.
Check out a question I answered the other day to help you visualize more clearly what I'm talking about.

Resources