How to manage loginViewControllers in an app programmatically? - ios

I am trying to use Firebase to create a mini social media. There are currently two components, a loginRegisterViewController, which handles login and registration, and a TabbarViewcontroler, which presents the main content.
In appDelegate's didFinishLaunching method, i set the TabbarViewcontroler as the rootView controller
let rootviewController = TabbarController()
window?.rootViewController = rootviewController
and in the TabbarController's viewDidLoad method, i implement the following code to see if a LoginViewController should be presented or not.
class TabbarController: UITabBarController {
var handle : FIRAuthStateDidChangeListenerHandle?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
// check if the user is logged in or not.
handle = FIRAuth.auth()!.addStateDidChangeListener(){ auth, user in
if user == nil {
self.present(loginRegisterViewController(), animated: true, completion: nil)
}else{
self.setupTabbarController()
}
}
}
}
If the user is logged in, we setup the tabbarViewController, if the user is not, we present the loginRegisterViewController on top of the tabbarViewController.
However, every time I register a new user and dismiss the loginRegisterViewController, I will get an empty tabbarViewController.
I understand that this happens because of the if-else condition in the viewDidLoad method of the tabbarViewController.
Since initially there is no user logged in, the
self.setupTabbarController()
method is thus not called.. Therefore, after i register a new user and dismiss the loginRegisterViewController, i get an empty tabbarViewController.
But, how do I go to solve this? Or is there a better way of checking if a user is logged in or not to present the mainContentViewController or LoginRegisterViewController?
Thanks in advance.
Edit : The setupTabbarViewController is a function to setup the tabbarController
func setupTabbarController(){
let shopControler = ViewController()
let shopNavigationControler = UINavigationController(rootViewController: shopControler)
shopNavigationControler.tabBarItem.title = "Shop"
shopNavigationControler.tabBarItem.image = UIImage(named: "Shop_Tabbar_Image")
viewControllers = [newsfeedNavigationControler, shopNavigationControler]
}

as you mentioned correctly we would need to call self.setupTabbarController() again, after the login state changes.
I usually go with this kind of implementation:
Create a class AuthManager or something like that. This has the users loginState and a function called configureRootViewController.
Declare a static sharedInstance.
In the AppDelegate - didFinishLaunchWithOptions:
Your call would look something lie this:
AuthManager.sharedInstance.configureRootViewController()
All that configureRootViewController() does is it checks the login state and configures the correct rootViewController.
Every time your login state changes you want to call this method again.
I hope that helped. Still rather early to give precise answers. :-)
Have a great day.

Related

How to simulate returning to a ViewController that has not been shown

I have an iOS application with a login screen. Once the user logs in, I use presentViewController to go to the main screen of the application. If the user logs out from the main screen, I dismiss the current (main) view controller to return to the login screen. This works fine and uses the standard animations for "present modally".
Now I want to modify this so that if the user is logged in already, the login screen is skipped. So in my application delegate I do the following (pseudo-code)
if (user logged in)
presentViewController(mainVC)
else
presentViewController(loginVC)
Question: If the user was already logged in and I show the main view controller directly, I cannot "dismiss" it anymore to return the login view controller (as this has never been shown). How can I then "simulate" returning to the login VC?
In Android, something similar can be achieved by manually manually building a "back stack" of activities (https://developer.android.com/training/implementing-navigation/temporal). Is there something equivalent to this in iOS ?
You have to check at application Launching. User logged in or not.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if isUserLoggedIn {
//Set Main ViewController
self.window?.showAuthenticationVC
} else {
//Optional.. But you can set here Logic VC
self.window?.showMainController()
}
return true
}
isUserLoggedIn Flag that manage user loggedIn or Not. You can store that value in UserDefaults.
Manage to rootViewController for loggedIn and loggedOff User.
extension UIWindow {
func showAuthenticationVC() {
self.makeKeyAndVisible()
let authenticationVC : LoginController = LoginController()
let navigationController = UINavigationController(rootViewController: authenticationVC)
self.rootViewController = navigationController
}
func showMainController() {
self.makeKeyAndVisible()
let authenticationVC : MainController = MainController()
let navigationController = UINavigationController(rootViewController: authenticationVC)
self.rootViewController = navigationController
}
}
And at time of login action. Set showMainController() using AppDelegate window. same of at the time of logout call showAuthenticationVC() using window property of AppDelegate.
Hope it's Help to you!
I have faced this type of problem several times. The way I handle it is I use a dummy VC at the start of the app naming it StartupVC. In the StartupVC I add the logic to check if user is logged in or not. If user is logged in I send it to the home screen. If not I present the login screen.
Now, when user logs out, I pop the view controllers up to the StartupVC. In the startup VC, I put the login check login in viewWillAppear method, so as soon as the StartupVC is shown again, the user is taken to login screen.
To make the transition smooth, you can turn off the animations when navigating back to login after logout.

ViewDidLoad is always called

I just recently started to use Swift and am facing a "weird" bug with the viewDidLoad method.
My very simple app currently only has 2 viewcontrollers:
MainViewController, which is the Apps entry point and serves as an overview for the data which has already been created. It also provides the option to add new data, which would trigger a segue to
DataViewController, which provides the UI to create new data, after which it goes back to the MainViewController
Now my issue is that the viewDidLoad method of MainViewController is always called whenever the MainViewController appears (At the start of the app and every time the DataViewController disappears). Particularly, the msg "MainViewController newly created" is always printed.
Even worse, it seems that my app is "secretly" resetting. To show this I have defined the class variable "createView" in my MainViewController which is true by default, and is set to false during the viewDidLoad (the only place where this variable is called/set). However the msg "MVC newly created" is still always printed in the output after the MainViewController shows up. How can that be? Why / how is createView reset to true?
Hope this snippet is enough to find the issue. Otherwise, let me know if something is missing.
Thanks for your help!
override func viewDidLoad()
{
super.viewDidLoad()
if (createView)
{
determineArraySize()
createDataArray()
print("MainViewController newly created")
createView = false
}
else {print("Nothing happened")}
}
As #moritz mentioned in the comments, check out the way you present the DataViewController in your storyboard.
If the view is presented modally, you should call:
dismiss(animated: true, completion: nil)
If the view is presented using a show seque, you should call:
_ = navigationController?.popViewControllerAnimated(true)

How to add a tabbar after a user action

I have an app that doesn't have to show any tab when the user isn't logged in. I'm facing many issues with this functionality. First of all, I had embed the views, after the login screen, into a tab bar controller and everything was showing ok, except when I had implemented the login feature. As it's an async call, I had to wait until the credentials were validated. I wasn't able to do this in the shouldPerformSegue method or any other method provided by apple because you can't block the main thread until the async stuff is done, so the segue has to be done programaticaly in an IbAction:
#IBAction func doLogin(sender: AnyObject) {
userIsLogged = false
let apiCall = webApi()
apiCall.callCheckIsUserLogged(nil, password : self.passwordField.text, email: self.mailField.text){ (ok) in
if ok {
if(userIsLogged == true){
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("loginUser", sender: self)
}
}else {
NSOperationQueue.mainQueue().addOperationWithBlock{
print("User not logged in")
self.alert.message = "Please enter valid credentials"
self.displayAlert()
}
}
}
}
}
But this has driven me to another issue: after the programatic segue, my tab bar controller was disappearing, and after read a while it looks like the only way to avoid this is to embed your tab bar controller into a navigation controller. So I did it, but now, I got many new issues. First of all I got two navigation controllers, the one who is at the very beginning of the project and this new one I have embed into the tab bar controller. A picture will illustrate this better than my words:
Now I have two navigation controllers, and I don't know how to hide the top one. Already tried:
self.navigationController?.navigationItem.hidesBackButton = true
But is hiding the arrow and I need to hide the other one navigation controller. But the best thing indeed would be to see the best approach for this kind of cases, when you want to add a tabBar controller embed into a navigation controller in the middle of the project.
Thanks all
I guess you can take another approach. Make login storyline and your app storyline distinct.
Have a storyboard for your login procedure, and another storyboard for your home (or whatever you like) and manage them in AppDelegate.
This is how i did it:
if /* user must log in */ {
self.window?.rootViewController = loginStoryboard?.instantiateInitialViewController()
self.window?.makeKeyAndVisible()
}
else {
self.window?.rootViewController = homeStoryboard?instantiateInitialViewController()
self.window?.makeKeyAndVisible()
}
Put this code in a method (for example called manageRootViewController()) and call it at app launch, or after your login. (You can also add custom animations if you like)

Load Data Before Adding ViewControllers to UITabBarController

In my application, once the user logs in from the LoginViewController, he is directed to the ProfileTabBarController.
ProfileTabBarController is a subclass of UITabBarController.It consists of three view controllers all of which need a reference to an instance of Profile.
When the ProfileTabBarController is pushed, it loads the user's profile. Once the profile loads successfully, it sets a reference to profile on each of its view controllers and then adds them as tab items.
The reason I chose this approach is that if the user launches the application and his token hasn't expired, the application should go directly to the ProfileTabController; the user need not login again. Rather than duplicate the code for loading the profile in both AppDelegate and LoginViewController, I felt it was better to encapsulate it in ProfileTabBarController.
The problem with this approach is that during the segue, the UITabBarController appears black as no view controller has been set. I managed to remedy this by creating a LoadingViewController and setting it initially as the only UIViewController of ProfileTabController
My question is whether there is a better approach to solving this problem. I don't really like the idea of having a UIViewController with no other purpose then to display a loading icon.
Presenting a tab view controller with no views doesn't make much sense, so I think your solution of giving it a view controller to handle this loading state makes perfect sense. Throw in some cool animation to keep the user entertained.
It's also possible that something could go wrong and the profile may not load properly. This transitional view controller seems like a good place to put the code to deal with possible errors.
I was able to eliminate the Loading ViewController by making use of NSNotificationCenter.
ProfileTabBarController adds the three ViewControllers and then begins loading the profile.
override func viewDidLoad() {
super.viewDidLoad()
self.setupUI()
self.setupViewControllers()
self.loadProfile()
}
Once loading completes, the ProfileTabBarController emits a ProfileDidLoad notification.
let completion = { (profile: Profile?) in
MBProgressHUD.hideHUDForView(self.view, animated: true)
if let profile = profile {
self.profile = profile
NSNotificationCenter.defaultCenter().postNotificationName(Notification.ProfileDidLoad, object: self.profile)
} else {
UIAlertView(
title: "",
message: NSLocalizedString("Error loading profile", comment: ""),
delegate: nil,
cancelButtonTitle: "OK"
).show()
}
}
The ViewControllers observe the ProfileDidLoad notification:
func setupNotificationObserver(){
NSNotificationCenter.defaultCenter().addObserver(self, selector: "profileDidLoad:", name: Notification.ProfileDidLoad, object: nil)
}
#objc func profileDidLoad(notification: NSNotification){
if let profile = notification.object as? Profile{
self.profile = profile
}
}
In this way, the view controllers are immediately displayed after login without the need of a loading screen.

Right way or event to choose what view load in swift

I'm working in an app that logs in an user if there isn't another user already logged in at launch time. This way the first view to appear should be the Login View. But in the case there is a logged user already, the first view appearing should be the main menu. Im handling this with the viewWillAppear function and it's working, but I don't know if this is the correct approach or how it should be handle in this situations.
Here is my code. My first view is MainMenuVC in which I control if there is a logged user or not, then I choose if stay in main menu view or push my login view.
class MainMenuVC: UIViewController {
override func viewWillAppear(animated: Bool) {
if (UserMgr.users.count == 0){
var vc1:LoginVC = self.storyboard?.instantiateViewControllerWithIdentifier("LoginView") as LoginVC
self.navigationController?.pushViewController(vc1, animated: false)
}
else
{
//I do nothing so this view is loaded
}
}
I don't know if i should use another ViewController and implement the function loadView() to decide what view load, but the problem is make that view work with the story board and my navigation controller.
Any suggestions?
Basically you will have two different view controllers, one for the login screen (VCLogin) and one for the main menu (VCMainMenu). Now, in your AppDelegate there are methods which are called, when the app launches respectively when it appears. So, place the code checking whether a user is logged in there and make the appropriate view controller the root view controller, e.g.
let navigationController = window.rootViewController as UINavigationController
navigationController.rootViewController =
userIsLoggedIn ? mainMenuViewController : loginViewController

Resources