After my app is finished launching, I want my app to open a LoginViewController if a current user is not logged in. Otherwise, it goes to a ViewController which is at the index of 0 (default value). So if not logged in, then show a modal view over UITabBarController.
My initial viewcontroller is a UITabBarController, which is created in UIStoryboard. It is check-marked as Initial View and is connected to other five viewcontrollers graphically.
Actually, I think I know the reason why a LoginViewController does not appear. It is because the value result was nil (I checked in the console). But why?? Other than this behavior, everything is working well. UITabBarController seems to be working without any problem.
My LoginViewController is embedded with UINavigationViewController. This code is inside a ViewController which is at a selected index of 0. It is put inside viewDidAppear() method
let main = UIStoryboard(name: "Main", bundle: nil)
let view = main.instantiateViewController(withIdentifier: "login") as! LoginViewController
let nav = UINavigationController(rootViewController: view)
self.tabBarController?.present(nav, animated: false, completion: nil)
However, it is not working.
Change the rootViewController of window to UINavigationController if user is not logged in and if he is change it to the tabBarController
You should implement the logic of deciding what is the desired initial view controller to be the root (based on whether the user logged in or not) before navigating to any view controller, application(_:didFinishLaunchingWithOptions:) method would be appropriate for such an implementation:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// let's assume that you are recognizing if the user logged in by a flag called 'isLoggedin':
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if isLoggedin { // show display main view controller
let mainViewController = storyboard.instantiateViewController(withIdentifier: "mainViewController")
// setup any needed config for mainViewController...
self.window?.rootViewController = mainViewController
} else { // display login view controller
let loginViewController = storyboard.instantiateViewController(withIdentifier: "loginViewController")
// setup any needed config for loginViewController...
self.window?.rootViewController = loginViewController
}
self.window?.makeKeyAndVisible()
return true
}
Or as a shorter version:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let isLoggedin = false
let initialViewController = storyboard.instantiateViewController(withIdentifier: isLoggedin ? "mainViewController" : "loginViewController")
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
return true
}
At this point, after the app finished launching (without jumping to any view controller yet), the initial view controller would be displayed based on the user logging in validation. I would assume that it would be a better behavior unless there is a specific requirement to present the login view controller on the of the tabbar controller; Logically, there is no even need to navigate to the tabbar controller if the user didn't login yet.
I run your code, Here is issue:
2018-01-18 16:23:52.845273+0800 try[8180:315334]
Warning: Attempt to present on whose view is not in the window hierarchy!
self.tabBarController?.present(nav, animated: false, completion: nil)
this line is very wired. I seldom saw.
So you can also let a child controller of self.tabBarController to present nav.
The choose one of the tab bar.
Related
Is it possible to limit the NavigationController to a certain set of UIViewControllers. See the image, I want the navigation controller, but only for the login/create user session. Once logged in, I obviously don't want the user to be able to go back (except log out). How can I accomplish that? I can't figure it out.
Go to Storyboard -> select NavigationController -> Attributes Inspector -> uncheck "Shows Navigation Bar" property
Then select relationship between Login/SignUp And TabBarController, and delete it.
Once login you can set your TabBarController(or any related controller) as the rootViewController. And when app launches you can check it in your AppDelegate.swift file like this,
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if Auth.auth().currentUser != nil {
let tabBarController = storyboard.instantiateViewController(withIdentifier: "tabBarcontroller") as! TabBarController
self.window?.rootViewController = tabBarController
self.window?.makeKeyAndVisible()
}
else
{
let loginNavController = storyboard.instantiateViewController(withIdentifier: "LoginNavController") as! UINavigationController
self.window?.rootViewController = loginNavController
self.window?.makeKeyAndVisible()
}
return true
}
You could change the stack of your navigation controller when needed, e.g:
func logIn() {
//Delete all presented view controllers up to a point
navigationController.setViewControllers([], animated: false)
//Create new view controller
let viewController = ....
navigationController.pushViewController(viewController, animated: true)
}
Take a look at setViewControllers, that may give you some idea.
If you want to leave previous view controllers in stack, and just forbid user to pop to them, then it may be the best solution to subclass UINavigationController and override func popViewController(animated: Bool) -> UIViewController?
I'm making twitter client app, using TwitterKit.
The initial VC is loginVC with login button. After login, it presents tableviewVC that shows list of tweets.
I want to redirect the user to tableviewVC directly if the user logged in already before and the session remains.
So I implemented codes in viewWillAppear that check logged-in users and present tableviewVC, but the tableviewVC never presented though the user session remains.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if TWTRTwitter.sharedInstance().sessionStore.hasLoggedInUsers() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let tweetVC = storyboard.instantiateViewController(withIdentifier: "TweetTableViewController")
present(tweetVC, animated: true, completion: nil)
}
}
But when I implemented the same codes in viewDidAppear, the tableviewVC showed up when the user session remains.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if TWTRTwitter.sharedInstance().sessionStore.hasLoggedInUsers() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let tweetVC = storyboard.instantiateViewController(withIdentifier: "TweetTableViewController")
present(tweetVC, animated: true, completion: nil)
}
}
I don't understand why it doesn't work when the codes are in viewWillAppear.
Could someone please explain it for me?
As the methods name explains,
viewWillApper means the view of the current viewController is going to be appear, still not on the window. So, in that case you cann't present the any viewController over the viewController which is not being displayed in the window.
viewDidApper means the view of the current viewController is being displayed on the window, That's why you're able to present the new viewController.
PS:
As you want to achieve, in case of already logged in show the listVC. I would suggest to setup the rootViewController of the window in didFinishLaunchingWithOptions method of AppDelegate.
Like:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
var rootVC: UIViewController?
if <check weather user is already logged in> {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
rootVC = storyboard.instantiateViewController(withIdentifier: "TweetTableViewController")
}
else {
rootVC = <you loginVC object>
}
self.window?.rootViewController = rootVC
self.window?.makeKeyAndVisible()
return true
}
I think it's because it's the initial VC that the app is still presenting, and viewWillAppear is called before the view is presented. So trying to present another view on top a view that's not presented yet won't work.
My solution to this was to load the Login view with the loginButton hidden then on viewDidAppear check if user is logged in then present the logged page otherwise you show the loginButton.
I want to provide a log on view controller so my users can authenticate prior to being presented the tab bar controller. In the code displayed below, I get the error "Could not cast value of type 'LogInViewController' to 'UITabBarController'. Apple documentation listed:
When deploying a tab bar interface, you must install this view as the
root of your window. Unlike other view controllers, a tab bar
interface should never be installed as a child of another view
controller.
I am stumped and my implementation is complicated when I am propagating core data across the 5 view controllers (e.g. controller1.coreDataStack = coreDataStack, declared as a property in the AppDelegate class) that is part of the tab bar controller. Can I get some help on how should I transition the user from the log in screen to one of the tabbed view controllers? Any input would be appreciated.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Login View Controller
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let loginVC = storyboard.instantiateViewController(withIdentifier: "loginVC") as! LogInViewController
self.window?.rootViewController = loginVC
// TabBar Controller
let tabController = window!.rootViewController as! UITabBarController
if let tabViewControllers = tabController.viewControllers {
// First tab (only one so far...)
let navController = tabViewControllers[0] as! UINavigationController
let controller1 = navController.viewControllers.first as! FirstViewController
controller1.coreDataStack = coreDataStack
}
}
You got error because trying to force unwrap LogInViewController to UITabBarController.
For your design flow store loginStatus of user in database an check condition on this before setting the window.rootViewController, if user is logged-in then show tabController, otherwise show LogInViewController.
let userLogined = GET LOGIN STATUS FROM DATABSE
if userLogined{
// Initiate Tabbar Controller object
let tabController = INITIATE_TABBAR_CONTROLLER
let tabViewControllers = tabController.viewControllers
// First tab (only one so far...)
let navController = tabViewControllers[0] as! UINavigationController
let controller1 = navController.viewControllers.first as! FirstViewController
controller1.coreDataStack = coreDataStack
self.window?.rootViewController = tabController
}else{
// Login View Controller
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let loginVC = storyboard.instantiateViewController(withIdentifier: "loginVC") as! LogInViewController
self.window?.rootViewController = loginVC
}
As an alternative method, how about always setting the rootViewController to the tabbed bar controller. Then if the user is logged out, modally present the login view controller from the tabbed bar controller. Once the user has successfully logged in, dismiss the login view controller.
I've used this method many times on many apps.
Suppose I have three view controllers inside a storyboard, I want to load all of them into view controllers stack, but I want to choose which one will appear first to the user. As shown below I would like to show the third view on load, instead to show the first, any glue?
Option 1. Using storyboards, you see the arrow pointing to your ViewController 1. Drag that to View Controller 2 or 3.
Option 2. On load of your first view controller, you can instantiate whichever view you'd like in your viewDidLoad(), provided you have given each storyboard view an ID.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "YourVCIdentifier")
self.present(controller, animated: true, completion: nil)
Option 3. In your AppDelegate file, you can do this.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
self.window = UIWindow(frame: UIScreen.main.bounds)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "YourVCIdentifier")
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
return true
}
UI flow:
AppDelegate
-->
LoginViewController (not in the storyboard)
-->
navigation controller (in the storyboard)
-->
PFQueryTableViewController (in the storyboard) named "OrdersVC"
This is the navigation controller with OrdersVC:
This is my AppDelegate:
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// ...
// initial VC
let VC = LoginViewController()
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window!.rootViewController = VC
window!.makeKeyAndVisible()
return true
}
The above works fine. Then, from LoginViewController, I am then trying to display my storyboard's initial VC which is a navigation controller hosting a PFQueryTableViewController. Note that LoginViewController is not in the storyboard.
let destVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("OrdersVC") as! UITableViewController
// will throw "unexpectedly found nil"
let navController = UINavigationController(rootViewController: destVC)
navController.pushViewController(destVC, animated: true)
// will throw "unexpectedly found nil"
self.presentViewController(navController, animated: true, completion: nil)
Problem is that in my PFQueryTableViewController's viewDidLoad and viewDidAppear the following statement is always nil:
// navitaionController is nil
self.navigationController!.navigationBar.translucent = false
So how can I correctly instantiate PFQueryTableViewController inside its navigation controller?
You are instantiating the OrdersVC instead of instantiating the navigation controller into which it is embedded and which is the "initial" view controller of your storyboard. Use instantiateInitialViewController instead of using the identifier.
let nav = storyboard.instantiateInitialViewController()
self.window!.rootViewController = nav
The reason for the confusion is that you are "unlinking" the initial view controller from the storyboard with your login controller. You have to add the initial view controller back to the main window.