How to pop from 3rd ViewController to the initial ViewController without NavigationControllers? - ios

I know this is probably an easy task but I can't get my head around how to solve it. I'm not using a NavigationController by the way. Basically I have 3 view controllers in my app:
-LoginVC (this has a register button. when tapped, it goes to the SignupVC)
-SignupVC (if user signs up, it will push to the MainAppVC)
-MainAppVC (has a logout button)
For the transitions, I use the method: present(viewController, animated:, completion:)
When the user logs in via the LoginVC, he'll be presented the MainAppVC as expected. When he logs out, I'll dismiss the current VC (which is the MainAppVC) to send him back to the LoginVC.
Now here is the case where I have questions about. When the user does not have an account and signs up, this is the VCs he will pass through (LoginVC > SignupVC > MainAppVC). Once he registers successfully, he'll be presented the MainAppVC. Now once he logs out, he'll be transitioned from the MainAppVC to the SignupVC because I used the same dismiss method.
What I want to do is to send the user back to the LoginVC from the MainAppVC. How do I accomplish that without using a navigation controller in my project? How do popular apps (Facebook, Twitter, Instagram, etc) handle this in their apps?
One way I can think of is to perform a segue from 3rd to 1st VC but I think that's a dirty way since it just adds to the stack, instead of popping it off which is a potential issue.

There are three solutions.
1.Use window root view controller.Set root view controller between login and main view controller.
let loginSb = UIStoryboard(name: "Login", bundle: nil)
let loginVc = loginSb.instantiateInitialViewController()
window!.rootViewController = loginVc
2.Dismiss the sign up view controller the first time you present main view controller.And present the main view controller from login view controller.
3.Try to dismiss the sign up view controller in the completion block when you dismiss the main view controller.
I think the first one is best.

If are forced to NOT use a rootViewController (with UINavigationController), you should check in MainAppVC which one is the previous VC (Pseudocode in the example) and do:
Note: Swift 3 Code.
// If previous VC is LoginVC
dismiss(animated: true)
// else if previous VC is SignupVC
// here's what you want
presentingViewController?.presentingViewController?.dismiss(animated: true)
Hope that helped.

Another option is to use unwind segues.
In the ViewController that you want to go back to, implement a method as follows
#IBAction func unwindToLogin(segue: UIStoryboardSegue) {
}
Then in Storyboard, from the last ViewController right click and drag from the button (for example) to Exit and choose unwindToLoginWithSegue.
Note that you can also do this programatically. Here is a good tutorial.

Best approach is :
Dismiss SignUpVC once User Register successfully, Then from LoginVC present MainVC.
In SignupVC :
self.dismissViewControllerAnimated(true, completion: {
self.LoginVC!.present(MainVC, animated: true, completion:nil)
})
This approach is used mostly.

Related

dismiss more than one View Controllers to return to the root ViewController in ios13

I have three view controllers written programmatically, the first VC is Sign in but if the user forgets the password this will led him to another two view controllers I need to return to the sign in VC directly form the third VC after the user finish the specific procedures.
you could do like this
self.presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil)
since are both vc to dissmiss it will return to login vc.
every presented viewController has a property called
PD: presentingViewController, is hold as reference to the viewController that is responsable for present it, so 2dn VC has a reference to 1st VC as presentpingviewcontroller, but also 3er VC have a reference to 2dn vc that has a reference to 1st VC so you call the above method chaining two presenting and ending in first VC, so you could present as many VC as you want as long as you know how many have been presented you could return to what ever you want.
You can use navigation controller, push your view controllers to navigation like
self.navigationController?.pushViewController(viewController: vc, animated: true)
and when you need to close all controllers use
self.navigationController?.popToRootViewController(animated: true)

Instantiate and present viewcontroller

I switch between views in my app with the following code:
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
settingsVC = storyBoard.instantiateViewController(withIdentifier: "SettingsVC") as! SettingsVC
self.present(settingsVC, animated: true, completion: nil)
This works fine, for instance from a main menu, after a press of a button. But I did this a few times after each other going back and forward from the menu to the settings view and I noticed that the view loads as many times as I went back and forward. So it probably creates the view controller again and again. How can I make sure it does in only once. So is there a way to check whether the view controller does exist. As a sidenote: I do not work with a navigation controller, just a few plain viewcontrollers.
Thanks in advance!
So is there a way to check whether the view controller does exist.
It doesn't exist. You yourself create it here, so you know it is created. But it is also destroyed when the opposite happens, i.e. when the view controller you present with present is dismissed. That is expected and correct. If you want state to be preserved between instantiations and restored on the next instantiation, it is up to you to preserve and restore it.
You have a few options. I, like Ayaz, recommend doing this in a navigation controller but if for some reason you don't want to, I would recommend dismissing the SettingsVC when you are done with it. You can do this by calling self.dismiss(animated: true, completion: nil) after hitting the "close" or "back" button.

How to segue to a different view controller on the last page in swift?

I have two different view controllers. One is programmatically created(slideshow) and the other is with the interface builder(Login page). On the slideshow, once the user reaches the last page, it should perform segue to the login page.
How can I make this work?
First you can't create a segue between a VC created in code and another created in IB ( as both should be in IB ) , you must present it like this
let login = self.storyboard?.instantiateViewController(withIdentifier: "loginID") as! loginViewController
and use
self.present(login, animated: true, completion: nil)
OR use this to completely clear the stack of shown VCs
UIApplication.shared.keyWindow?.rootViewController = login
If two view controller don't need to transmit data, you can just change window root view controller to login page. Of course, with animation would be better.

ViewControllers are not destructing

I've got a serious problem with my iOS app.
I have a login logic in my application. When logging in and then logging out, some view controllers are not destructing. This causes some issues, for example, some events that I emit using NSNotifcationCenter are emitted few times. These issues are avoidable, but I really want a solution to avoid some view controllers to stay open in the background without me controlling it.
The way control the login logic is as follows:
In the app delegate start function, if the user is already logged in, I set the root view controller to the main usable view controller. Therefore, I'm not doing anything and the root view controller is set to the login view controller navigation controller through the storyboard.
When the user logs off, I use a modal segue to transition the view controller back to the login view controller navigation controller.
As you may understand I'm using storyboards, swift and the newest iOS.
My logout code is segue that take me to the LoginViewControler:
self.performSegueWithIdentifier("Logout", sender: self)
My app delegate code:
if (userDefaults.valueForKey("uid") != nil) {
let tabBarView = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("TabBarViewController") as! TabBarViewController
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window?.rootViewController = tabBarView
}
What am I doing wrong?
I would appreciate help :)
EDIT
I even tried just setting the root view controller in the logout action and that didn’t help either. How’s that even possible?
This is how I do the logout now:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let newRootViewController = self.storyboard?.instantiateViewControllerWithIdentifier("LoginNavigationController") as! UINavigationController
appDelegate.window!.rootViewController = newRootViewController
Adam H. is right. If that doesn't work, then check for IBOutlets and delegates that have strong relationships, and change them to weak relationships. i.e.
#IBOutlet weak var collectionView: UICollectionView!
Without the weak keyword the view controller will never be disposed.
Depending on how your project is setup, if you are using a navigation controller (which I recommend) every time someone logs out you would put
dispatch_async(dispatch_get_main_queue()) {
self.navigationController.popToRootViewControllerAnimated(true)
}
That will pop everything off the navigation stack, which will dispose of all view controllers (unless you have strong relations, then they won't be disposed)
No matter how you choose to manage your trasitions , don't forget to add/ remove the observer whenever the view controller apear/disappear.
If the logged in screen presents the login screen and the login screen presents the logged in screen then you will have a cycle that keeps piling on new view controllers. To solve this, one must not present the other, but unwind to it. Another possibility is to hold instances of each as singletons and only present those.
I implemented something like that not long ago and to me it seems you're abusing the UINavigationController life cycle.
After reading your question twice, if I understand it correctly, it seems you're initializing your login view controller as a UINavigationController which stacks-up view controllers. Once user logs out, you're keeping the stack, adding more ViewControllers to the stack using the performSegue.
You can avoid it by using two different scenes -
1) Login View Controller which stands by it self.
2) Main flow of your app - can start with UITabController/ UINavigationController, both or whatever.
In AppDelegate you check -
If user is logged in - do your logics and set the app rootVC to the main flow vc.
Otherwise you set the loginVC (UIViewController) to be the root.
This also allows you to pop the login VC anywhere in the main flow, when needed, without interfering with the main flow.
In your case the loginVC is always UINavigationController's root so you must popToRootVC every time you wish to see it or performSegue to it which is worse because then you create another instance of a UINavigationController and resources never get deallocated.
Obviously in programming, in most cases, there are many solutions to one problem. I'm sure your problem can be solved using your flow. I just think it's bad experience to stack a loginVC over a navigation controller.
Part of the problem is that setting a new rootViewController on the UIWindow doesn't remove the view hierarchy from the old root view controller. That leaves all sorts of strong references hanging around, and if you use Xcode's view debugging, you can see that the the old view hierarchy is still sitting there, behind the new rootViewController's view hierarchy.
Something like this should fix the problem for you and allow your view controllers to deinit:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let newRootViewController = self.storyboard?.instantiateViewControllerWithIdentifier("LoginNavigationController") as! UINavigationController
appDelegate.window??.rootViewController?.view.removeFromSuperview()
appDelegate.window??.rootViewController?.dismissViewControllerAnimated(false, completion: nil)
appDelegate.window??.rootViewController = newRootViewController
get rid of ARC file by file in build settings or wholesale
per project (it seems that you can have non ARC project but have weak references
while at it: not sporting I suppose but you can have both).
Then override retain and release in the problematic view controller
and see who hold the extra reference by breaking in the overriden
retain and release. It should be an eduficational experience.
The lazy approach is to kill ARC just for the VC in question.
I'd be curious to see how this works for VCs written in swift ;-)
Me thinks it's yet another reason to stay in objc domicile a while longer
until/if swift compiler and runtime solidifies (if ever).
Hope this helps anyone.
PS: It takes forever to compile some swift file in my project and I have NO
idea which swift file is causing this. Duh.
As yet indicated there aren't a lot informations for provide correctly the solution to your question.
I can suggest you to change your approach. I made a similar workflow using a UINavigationController (navigationController) launched from AppDelegate, inside of if we are logged in I put as ViewControllers :
(where self is navigationController and rootViewController is another UINavigationController)
self.setViewControllers[loginViewController, rootViewController]
If your are not logged in you put only loginViewController:
self.setViewControllers[loginViewController]
in this case you can put the rootViewController where the user is logged in.
This is my 2cent.
I like having a root VC which is just blank. When the app starts, root VC immediately displays login VC as a child VC of root VC. When the user successfully authenticates, the login VC notifies root VC, which then adds main VC as a child of root VC, transitions (with a nice animation) from login VC to main VC (using [self transitionFromViewController: toViewController: duration: options: animations: completion:]), and then removes login VC as a child and discards it. On logout, main VC notifies root VC which then does the same thing in reverse. So most of the time you only have either login VC or main VC instantiated; the only time they are both instantiated is during the transition.
I find segues are useful for building quick prototypes, but for production apps I prefer not to use them.
I Guess the ViewControllers Stack of you will go like this:
- 1st launch: LoginVC
- After login: LoginVC - TabarVC
- Click Logout: LoginVC - TabarVC - LoginVC
....
So your below code should work:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let newRootViewController = self.storyboard?.instantiateViewControllerWithIdentifier("LoginNavigationController") as! UINavigationController
appDelegate.window!.rootViewController = newRootViewController
but it's not:(. In my opinion, You should always let the tabarVC rootViewController. And check in TabarVC, if user is not loged-in or user pressed logout, Present loginVC and dismiss it instead of performSegue.
You must remove your :
self.performSegueWithIdentifier("Logout", sender: self)
Instead of it, you can overriding this segue method, this is enough:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
userDefaults.removeObjectForKey("uid")
if segue.identifier == "Logout" {
let newRootViewController = segue.destinationViewController
// newRootViewController is optional in case you want to pass vars
// do whatever you want with your newRootViewController
}
}
About your NSNotification there are two methods to remove it:
NSNotificationCenter.defaultCenter().removeObserver(self, name: "NotificationIdentifier", object: nil)
NSNotificationCenter.defaultCenter().removeObserver(self) // Remove from all notifications being observed
In Swift, you can put the removeObservers in the new and special deinit method.

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

Resources