Limit Navigation Controller to a set of UIViewControllers - ios

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?

Related

Swift - Why should I use viewDidAppear rather than viewWillAppear when redirect user if the user logged in already

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.

Load and Present a second viewcontroller at launch

I am trying to present a second viewcontroller on top of the first at startup without dismissing the first. This post Swift 3 - loading multiple ViewControllers at launch certainly looks like it has the answer. It recommends:
Add in your main view controller
var secondViewController:UIViewController!
And in your viewDidLoad:
secondViewController: UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "yourIdentifier") as! SecondViewController
That's it. When you want to present it, use:
self.present(secondViewController, animated: true, completion: nil)
This third line works great if, for example, I attach it as an action to a button. However, it does not work if it is in viewDidLoad: of the first viewController. This is what I need.
How can I automatically present the second viewController on top of the first viewController at launch?
You need to do it at the proper place in your view controller. There are several methods that gets called when your view controller is presented, and they are meant for different tasks.
To present another view controller you should put it in viewWillAppear: or viewDidAppear:, as viewDidLoad: is too early in the presentation.
Read more about these methods here:
https://stackoverflow.com/a/5659007/4543629
I think this depends on what you are trying to do. If you want to push that ViewController at launch you could also try to present it from your AppDelegate in func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
Something like: (like i said it depends on your use case)
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let secondViewController = storyboard.instantiateViewController(withIdentifier: "yourIdentifier") as! SecondViewController
let navigationController = UINavigationController(rootViewController: SecondViewController)
self.window?.makeKeyAndVisible()
self.window?.rootViewController?.present(navigationController, animated: false, completion: {() -> Void in
mainMenuVC.tabBarControl = tabBarController
})
return true
}
You can go to AppDelegate and setup window or if you workong with storyboard drag arrow to ViewControler what you want
Yes, that first comment by LGP does the trick. Thank you. I present it here as an answer. I added this function to the first viewController:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
self.present(secondViewController, animated: true, completion: nil)
}
I did not look into the appropriateness of true or false in calling super.viewDidAppear(true) if anyone wishes to comment.

Swift: Presenting a viewController over UITabBarController

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.

How to load all view controllers storyboard, but skip the natural sequence and go direct to a specific view?

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
}

LaunchScreen.storyboard not opening Main.storyboard Navigation Controller

I have just started to develop a new application using Swift (newbie). I have
LaunchScreen.storyboard with just a image of my splash screen
I have a Main.storyboard with a Navigation Controller connected to two segues, Home and Registration.
In the ViewController.swift, inside the viewDidLoad I am deciding which segue to call
My Main.Storyboard does not have a rootViewController, I need to decide which viewController to display at run time.
if (Settings.hasRegistrationCompleted()) {
performSegue(withIdentifier: "Home", sender: nil)
} else {
performSegue(withIdentifier: "Registration", sender: nil)
}
My questions
I put a breakpoint on the first line if (Settings.has and the breakpoint never reaches here
LaunchScreen lasts only for 2 seconds (tested on my Simulator) how do I increase it
EDIT
I have Main set as the Main Interface on my project. I did a Clean build and tried again, did not work.
Also below is the Main.Storyboard
in here two things you need to identify
first
check your storyboard name Main.storyboard are attached properly in your Target -> general -> Deployment Info -> main Interface, for e.g like this
second
check your Intial VC connected with navigation Controller and ensure your Initial VC as Root controller
update answer
initially set Stroryboard ID and for each VC
there after change the Root controller in appdelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
self.window = UIWindow(frame: UIScreen.main.bounds)
// Override point for customization after application launch.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let rootViewController: UIViewController?
if (Settings.hasRegistrationCompleted()) {
rootViewController = storyboard.instantiateViewController(withIdentifier: "HomeVC")
}else
{
rootViewController = storyboard.instantiateViewController(withIdentifier: "RegistrationVC")
}
let navigation = UINavigationController(rootViewController: rootViewController!)
self.window?.rootViewController = navigation
self.window?.makeKeyAndVisible()
return true
}
There you must be have One View-Controller as a RootVieController of your NavigationController and it must be initialize with arow like following screenshot. From The Login you need to segue to two View Controller.
Like Following
So you need to check in LoginViewController that you are already logged in or not. Or you can segue to register
You can also Subclass UINavigationController and set it to the storyboard. Remove the segues (you don't need them)
Then
class ViewController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
if (Settings.hasRegistrationCompleted()) {
let homeVC = self.storyboard?.instantiateViewController(withIdentifier: "HomeVC")
self.setViewControllers([homeVC!], animated: false)
}
else {
let regVC = self.storyboard?.instantiateViewController(withIdentifier: "RegistrationVC")
self.setViewControllers([regVC!], animated: false)
}
}
}

Resources