advice on UIViewController hierarchy and flow - ios

I'm spending a lot time reading apple View Controller Programming but still can't get a clue of how to code for my flow.
I'm using ViewController, PageViewController, and Storyboard.
My rootViewController is a ViewController
I want two more ViewController
first, PageViewController to show pages of tutorials and last page has signup with presentViewController login
secondViewController, ViewController is the main content of the app
if case user has token in keychain will go to secondViewController.
After signup or login lead to secondViewController.
I didn't use navigation controller. Wonder this can be done by container view controller?
current problem In signupViewController self.parentViewController is pageViewController but I can't get the rootViewController, to dismiss and add secondViewController
P/S but In loginViewController self.presentingViewController I got the rootViewController!! it is out of my expectation.. I wonder why???

Set secondViewController as your rootViewController of UIWindow in AppDelegate. SecondViewController contains the base view content without data about login, so you can launch as soon as you can.
If user has token in keychain, just to update SecondViewController view with login status.
If user has no token in keychain, show PageViewController on SecondViewController, with presenting a viewController or just addSubview. After PageViewController, present loginViewController, when login finish, dismiss the loginViewController, update SecondViewController view with login status which is same with step2

You use storyboard, so I assume you'll also use segue. Your PageViewController can be the entry point, with a segue to secondViewController.
If you have the token in the keychain, you can programatically perform the segue using:
[self performSegueWithIdentifier: #"MySegue" sender: self];
Otherwise users stay on PageViewController to serve its original flow.

Related

Popping existing view controller and loading parent view controller

I have a use case where a view controller (e.g Source) loads other view controller (Login) using performSegueWithIdentifier
As name suggests LoginViewController is supposed to take user credentials, authenticate the user and load SourceViewController .
I created a delegate LoginDelegate which is implemented by SourceViewController. LoginViewController can successfully call delegate.onSuccessfulLogin() and delegate.onFailedLogin(). However, SourceViewController is not appearing.
Since, I didn't run any statements which give control of screen back to SourceViewController, I think I am running into this issue.
What is the best way, in such scenarios, to pop existing controller (LoginViewController) and give control (screen space) back to Source controller (SourceController)
Call dismissViewControllerAnimated(true, completion: nil) or navigationController?.popViewControllerAnimated(true) from the LoginViewController after calling delegate.onSuccessfulLogin() or delegate.onFailedLogin()
It will make you go back to the SourceViewController

Perform segue to a view that may not have been created

I have an app with 4 views, CheckAuthenticationView, HomeView, LoginView and DetailsView.
When the user starts the app the first view loaded is CheckAuthenticationView. This view checks the keychain for login information and if it exists, attempts to log the user in automatically and if successful, segues to DetailsView. If no information exists it performs a segue to the HomeView from which a user can then proceed to login.
Now on the DetailsView I have a button so the user can log out. If tapped, this clears any login information and performs an unwind (exit) segue to HomeView.
If a user does not logout but kills the app (restart phone etc), CheckAuthenticationView should produce a successful result meaning DetailsView will be loaded automatically. If this is the case, when I tap logout, the segue does not fire and the user remains on the DetailsView.
I am guessing that the segue does not happen because HomeView has not actually been created so there is nothing to segue to. Is there a way to segue to a view that has not yet been created?
Thanks
What do you mean by not yet created? If you haven't created the ViewController or any sort of view in your storyboard, then no you cannot segue to a view that does not exist. If you have created it then you can just call:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:storyboardName bundle:nil];
UIViewController *homeView = [storyboard instantiateViewControllerWithIdentifier:#"HomeView"];
[self presentViewController:vc animated:YES(or NO) completion:nil];

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.

Pop to rootViewController and then load a new viewController

I have a login screen(embeded in navigation controller) with 2 buttons: "Register" & "Login".
When the user press register and has successfully registered I want to to load the Login controller.
What I have in mind:
1) Draw a segue from RegisterVC to LoginVC and by pass the rootVC. This is not seems right to me
2)Save a property in NSDefaults then poptoNVC from register and have the viewWillAppear to check if it will executed the segue to LoginVC automatically.This is also does not seems to me right.
Is there any way to get a pointer to rootVC from RegisterVC executed the poptoNVC and as soon as poptoNVC is finished use the pointer to execute a segue to login?
Do you have any suggestions?
I suggest you to open the register view controller with
presentViewController (or use segue) type modal instead push the viewController.
When you dismiss the registerViewController, use:
[self dismissViewControllerAnimated:YES completion:^{
[_delegate goToLogin];
}];
so basically you can use the delegation pattern to say to the rootViewController (that is the delegate) that the registerViewController is dismissed and can access to the loginViewController.
OR: You can use your check in viewWillAppear but is not elegant for me.

Issue loading View Controller from UITabBarController

In the past my app has had only 1 main view controller (MainViewController) and a login view controller (LoginViewController) but now I am moving to a Tab Bar Controller.
Before I was able to do a simple check viewDidLoad of MainViewController for the existence of a username and password in the key chain. If a username and password was not present I used a segue to pop up a modal login view controller.
With the new setup of using a Tab Bar Controller I still only have 1 view controller (MainViewController) which is the root view controller (as of now) and I am trying to do the same thing where it pops up modal of the login screen.
Now when I call the segue in the viewDidLoad of MainViewController:
[self performSegueWithIdentifier:#"loadLoginView" sender:nil];
I am getting this error:
Warning: Attempt to present <LoginViewController: 0x1757cd80> on <UITabBarController: 0x17571e50> whose view is not in the window hierarchy!
But if I associate a button to a method that loads the LoginViewController by way of a segue it works fine. I am doing that in the MainViewController like this:
-(void)loadLogin
{
[self performSegueWithIdentifier:#"loadLoginView" sender:nil];
}
I can see from the error message that when I try to perform the segue from the viewDidLoad of MainViewController it's trying to load the LoginViewController from UITabBarController.
Why can I not load the LoginViewController from the viewDidLoad of MainViewController?
Any help with this would be great.
Thanks!
It looks like -viewDidLoad is getting called before your view controller stack is added to the window. Two things you could try are:
Delaying until the next time through the run loop (this should give the view controllers time to get in place) [self performSelector:#selector(loadLogin) withObject:self afterDelay:0];. This method won't allow you to call a method with two arguments directly
You could use -presentViewController: animated: completion:. This will cause your login controller to slide in from the bottom.

Resources