Tab bar not showing on home page after login - ios

I have implemented a firebase login and want to show my home page with a function, my problem is when showing the home page it will not show the tab bar and when showing the tab bar as initial VC it shows blank, but when I set the tab bar controller as initial VC within the settings it will show the tab bar but bypass the login, how can I get my home page to show the tab bar within my function?
#IBAction func LoginButtonTapped(_ sender: Any) {
// validate feilds still to impliment
//if all feilds valid do below
let email = EmailFeild.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let password = PasswordFeild.text!.trimmingCharacters(in: .whitespacesAndNewlines)
// sign in user
Auth.auth().signIn(withEmail: email, password: password) { (result, error) in
if error != nil{
// sign in error
self.ErrorMsg.text = error!.localizedDescription
self.ErrorMsg.alpha = 1
}else{
// go to home screen
self.transitionHome()
}
}
}
func transitionHome(){
let homeViewController =
storyboard?.instantiateViewController(identifier: Constants.Storyboards.homeViewContrl) as? HomeViewController
view.window?.rootViewController = homeViewController
view.window?.makeKeyAndVisible()
my attempt at setting tab bar as root VC leads to a black screen but when i set it as inital VC and bipass the login the home view shows the tab bar
// let tabViewController =
// storyboard?.instantiateViewController(identifier: Constants.Storyboards.homeNav) as?
//MainTabBarController
//view.window?.rootViewController = tabViewController
//view.window?.makeKeyAndVisible()
}
}

You just need to add UITabBarController in between, assign it to Class property of your TabController in storyboard and set it to root instate of directly using UIViewController. Refer below code snippet:
let tabViewController =
storyboard?.instantiateViewController(withIdentifier: "TabController") as? TabController
view.window?.rootViewController = tabViewController
view.window?.makeKeyAndVisible()
It will work as expected!!

Related

I am having a problem with checking if my users are signed in to Firebase and changing the the initial view controller

My goal is to check if my users have signed in to my app in Firebase before. Then change the initial view controller from my Navigation controller to my TabBarController. I'd like to do this for a better user experience so they don't have to log in every time.
Also where is the best place to put this code? my first View controller or my app Delegate?
if Auth.auth().currentUser != nil {
// User is signed in.
func transitionToTab() {
let tabBarController =
storyboard?.instantiateViewController(identifier: Constants.Storyboard.TabBarController) as? TabBarController
view.window?.rootViewController = tabBarController
view.window?.makeKeyAndVisible()
}
} else {
// No user is signed in.
func tranitionToView() {
_ =
storyboard?.instantiateViewController(identifier: Constants.Storyboard.HomeViewController) as? ViewController
view.window?.makeKeyAndVisible()
}
}
Your problem is you don't call the functions within the if/else statement.
I would recommend creating an additional slash screen that replicates the one within launchscreen.storyboard to initiate your authentication process. Your code should look similar to this;
if Auth.auth().currentUser != nil {
// User is signed in.
let tabBarController =
storyboard?.instantiateViewController(identifier: Constants.Storyboard.TabBarController) as? TabBarController
view.window?.rootViewController = tabBarController
view.window?.makeKeyAndVisible()
} else {
// No user is signed in.
storyboard?.instantiateViewController(identifier: Constants.Storyboard.HomeViewController) as? ViewController
view.window?.makeKeyAndVisible()
}
you can use this function from app delegate method
"didFinishLaunchingWithOptions"
and make an userdefaults to store current user object or any check that you
will true once user login into the app and in Appdelegate "didFinishLaunchingWithOptions"
check if it's true than set initial page
tabbarcontroller else show login screen.

How to move login page when session expired

I have multiple screens and api's, if session expired each api get session expired message. Based in that I'm moving from current page to login page.
My code :
//If session expaired move to login page
if message == "Session Expired" {
//Session Expired
DispatchQueue.main.async {
let lpc = self.storyboard?.instantiateViewController(withIdentifier: "LVC")
//Set the user login key false
UserDefaults.standard.set(false, forKey: "isUserLoggedIn")
//Clear user defaults
SharedClass.sharedInstance.clearDataFromUserDefaults()
self.navigationController?.pushViewController(lpc!, animated: false)
}
}
Here I'm using ** pushViewController** to navigate back. Every thing working fine, but when i'm in Nth VC is session expired it's navigating N times to login page. Means if I'm navigating from 1st VC to 2nd VC, 2nd VC to 33rd VC, 3rd VC to 4th, is session expired in 4th VC it's navigating to login page around 3times. How to resolve this issue....
You can try this
let vc = self.storyboard?.instantiateViewController(withIdentifier: "LVC")
self.navigationController?.setViewControllers([vc], animated: true)
Since you didn't provide your full code so I am not sure why that issue happens. However, here are 2 solutions to achieve your requirement.
First implementation: Image for 1st flow
If the user logged in:
navigationController?.viewControllers = [loggedInVC]
If the user doesn't login yet or when he/she logout:
navigationController?.viewControllers = [logInVC]
2nd implementation: Image for 2nd flow
LoginVC is the root view controller and acts as a loading screen, and only show the login form when the session is invalid. With this implementation, every time the session is invalid. You can call:
navigationController?.popToRootViewController(animated: true)
to navigate the user back to the login screen.
First create the global variable to access the AppDelegate, because we are putting redirection function here.
let appDelegate = UIApplication.shared.delegate as! AppDelegate
Now put below function in AppDelegate to set login screen whenever your session is expired.
func configureWindow(_ viewController: UIViewController) {
if let window = window {
window.rootViewController = viewController
window.makeKeyAndVisible()
}
}
now set your login UIViewController as like below.
let loginVC = LoginViewController()
appDelegate.configureWindow(loginVC)
use the following:
self.navigationController?.popToRootViewController(animated: false)
if login is not root,then you can use the following:
if let controllers = self.navigationController?.viewControllers, controllers.count > 0{
for vc in controllers{
if vc is LoginViewController{
self.navigationController?.popToViewController(vc, animated: false)
}
}
}
I don't know is this right or wrong. But i'm getting one more issue
1) First i cleared all navigations from navigation stack
2) Then i made my LoginVC as Root VC
3) Finally i called popToRootViewController
//If session expaired move to login page
if message == "Session Expired" {
DispatchQueue.main.async {
//Check navigation stacks
let navigationArray = self.navigationController?.viewControllers //To get all UIViewController stack as Array
print(navigationArray!)//Prints navigation stacks
//Remove all
self.navigationController!.viewControllers.removeAll()
//Check navigation stacks
let navigationArray2 = self.navigationController?.viewControllers //To get all UIViewController stack as Array
print(navigationArray2 as Any)//Prints nill
//Check whether the user logined or not
UserDefaults.standard.set(false, forKey: "isUserLoggedIn")
//Clear user defaults
SharedClass.sharedInstance.clearDataFromUserDefaults()
let lvc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LVC") as! LoginViewController
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = lvc
}
}
//Call alert function
self.showAlert(title: "", msg: message)
I don't know is this right or wrong. But i'm getting one more issue
Warning: Attempt to present <UIAlertController: 0x7fb840800600> on <******: 0x7fb840069800> whose view is not in the window hierarchy!

Accessing a modal viewController from Appdelegate

I have a situation where if the user is not logged in to Firebase, when they launch my app, a modal viewcontroller is presented asking them to log in.
I am also using UIActivityViewController to send data between devices, but want to notify the user with an alert if he is not logged in.
I figure I can do this from the AppDelegate in the open url application function:
func application(_ app: UIApplication, open url: URL, ...
I just have to figure out how to navigate to that viewcontroller to call the function that will present my alert.
Within this function, I have this:
if status == .notLoggedIn {
guard
let rootVC = window?.rootViewController as? UITabBarController,
let collectionSplitVC = rootVC.viewControllers?.first as? UISplitViewController,
let navVC = collectionSplitVC.viewControllers.first as? UINavigationController,
let MyBookshelfCollectionVC = navVC.children.first as? MyBookshelfCollectionVC
// here is where I want to get access to the modal viewController presented on this MyBookshelfCollectionVC.
// it is called LoginVC
else { return true }
LoginVC.showAlertFromAppDelegate(status: status)
}
So the question his, how do I access this modal viewcontroller?

Opening ViewController In AppDelegate While Keeping Tabbar

In my Xcode project when a user taps on a notification I want to first send them to a certain item in my tabBar then I want to instantiate a view controller and send an object over to that view controller. I have code the that sends them to the tabBar I want, but I do not know how to instantiate them to the view controller while keeping the tabBar and navigation bar connected to the view controller. All the answers on this require you to change the root view controller and that makes me lose connection to my tabBar and navigation bar when the view controller is called.
A Real Life Example of this: User receives Instagram notification saying "John started following you" -> user taps on notification -> Instagram opens and shows notifications tab -> quickly send user to "John" profile and when the user presses the back button, it sends them back to the notification tab
Should know: The reason why I'm going to a certain tab first is to get that tab's navigation controller because the view controller I'm going to does not have one.
Here's my working code on sending the user to "notifications" tab (I added comments to act like the Instagram example for better understanding):
if let tabbarController = self.window!.rootViewController as? UITabBarController {
tabbarController.selectedViewController = tabbarController.viewControllers?[3] //goes to notifications tab
if type == "follow" { //someone started following current user
//send to user's profile and send the user's id so the app can find all the information of the user
}
}
First of all, you'll to insatiate a TabBarController:
let storyboard = UIStoryboard.init(name: "YourStoryboardName", bundle: nil)
let tabBarController = storyboard.instantiateViewController(withIdentifier: "YourTabBarController") as! UITabBarController
And then insatiate all of the viewControllers of TabBarController. If your viewControllers is embedded in to the UINavigationController? If so, you'll to insatiate a Navigation Controller instead:
let first = storyboard.instantiateViewiController(withIdentifier: "YourFirstNavigationController") as! UINavigationController
let second = storyboard.instantiateViewiController(withIdentifier: "YourSecondNavigationController") as! UINavigationController
let third = storyboard.instantiateViewiController(withIdentifier: "YourThirdNavigationController") as! UINavigationController
Also you should instantiate your desired ViewController too:
let desiredVC = storyboard.instantiateViewController(withIdentifier: "desiredVC") as! ExampleDesiredViewController
Make all of the NavigationControllers as viewControllers of TabBarController:
tabBarController.viewControllers = [first, second, third]
And check: It's about your choice.
if tabBarController.selectedViewController == first {
// Option 1: If you want to present
first.present(desiredVC, animated: true, completion: nil)
// Option 2: If you want to push
first.pushViewController(desiredVC, animated. true)
}
Make tabBarController as a rootViewController:
self.window = UIWindow.init(frame: UIScreen.main.bounds)
self.window?.rootViewController = tabBarController
self.window?.makeKeyAndVisible()
Finally: It's your completed code:
func openViewController() {
let storyboard = UIStoryboard.init(name: "YourStoryboardName", bundle: nil)
let tabBarController = storyboard.instantiateViewController(withIdentifier: "YourTabBarController") as! UITabBarController
let first = storyboard.instantiateViewiController(withIdentifier: "YourFirstNavigationController") as! UINavigationController
let second = storyboard.instantiateViewiController(withIdentifier: "YourSecondNavigationController") as! UINavigationController
let third = storyboard.instantiateViewiController(withIdentifier: "YourThirdNavigationController") as! UINavigationController
let desiredVC = storyboard.instantiateViewController(withIdentifier: "desiredVC") as! ExampleDesiredViewController
tabBarController.viewControllers = [first, second, third]
if tabBarController.selectedViewController == first {
// Option 1: If you want to present
first.present(desiredVC, animated: true, completion: nil)
// Option 2: If you want to push
first.pushViewController(desiredVC, animated. true)
}
self.window = UIWindow.init(frame: UIScreen.main.bounds)
self.window?.rootViewController = tabBarController
self.window?.makeKeyAndVisible()
}
If you want to present or push ViewController when the notification is tapped? Try something like that:
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
switch response.actionIdentifier {
case UNNotificationDefaultActionIdentifier:
openViewController()
completionHandler()
default:
break;
}
}
}
I can think of two ways to do that:
1) If that view controller is a UINavigationController you can simply push the profile from wherever you are:
if let tabNavigationController = tabbarController.viewControllers?[3] as? UINavigationController {
tabbarController.selectedViewController = tabNavigationController
let profileViewController = ProfileViewController(...)
// ... set up the profile by setting the user id or whatever you need to do ...
tabNavigationController.push(profileViewController, animated: true) // animated or not, your choice ;)
}
2) Alternatively, what I like to do is control such things directly from my view controller subclass (in this case, PostListViewController). I have this helper method in a swift file that I include in all of my projects:
extension UIViewController {
var containedViewController: UIViewController {
if let navController = self as? UINavigationController, let first = navController.viewControllers.first {
return first
}
return self
}
}
Then I would do this to push the new view controller:
if let tabViewController = tabbarController.selectedViewController {
tabbarController.selectedViewController = tabViewController
if let postListViewController = tabViewController.containedViewController as? PostListViewController {
postListViewController.goToProfile(for: user) // you need to get the user reference from somewhere first
}
}
In my last live project, I'm using the same approach like yours. So even though I doubt this method is the correct or ideal for handling a push notification from the AppDelegate (I still got a lot of stuff to learn in iOS 🙂), I'm still sharing it because it worked for me and well I believe the code is still readable and quite clean.
The key is to know the levels or stacks of your screens. The what are childViewControllers, the topMost screen, the one the is in the bottom, etc...
Then if you're now ready to push to a certain screen, you would need of course the navigationController of the current screen you're in.
For instance, this code block is from my project's AppDelegate:
func handleDeeplinkedJobId(_ jobIdInt: Int) {
// Check if user is in Auth or in Jobs
if let currentRootViewController = UIApplication.shared.keyWindow!.rootViewController,
let presentedViewController = currentRootViewController.presentedViewController {
if presentedViewController is BaseTabBarController {
if let baseTabBarController = presentedViewController as? BaseTabBarController,
let tabIndex = TabIndex(rawValue: baseTabBarController.selectedIndex) {
switch tabIndex {
case .jobsTab:
....
....
if let jobsTabNavCon = baseTabBarController.viewControllers?.first,
let firstScreen = jobsTabNavCon.childViewControllers.first,
let topMostScreen = jobsTabNavCon.childViewControllers.last {
...
...
So as you can see, I know the hierarchy of the screens, and by using this knowledge as well as some patience in checking if I'm in the right screen by using breakpoints and printobject (po), I get the correct reference. Lastly, in the code above, I have the topMostScreen reference, and I can use that screen's navigationController to push to a new screen if I want to.
Hope this helps!

Link UIButton To A Specific Tab Within Several Different Tab Bar Controllers

I have an 'Enter' button on my app index/main page.
This button links to one of two (2) different tab bar controllers - depending on whether the user has purchased full access or not. Each tab bar controller has 3 tabs.
These two (2) tab bar controllers are named "TabBarControllerFree" and "TabBarControllerPaid" respectively.
I have written the following code to determine where the user is 'sent':
#IBAction func Enter(sender: AnyObject) {
//Check if product is purchased
if (defaults.boolForKey("purchased")){
print("User has purchased")
// Send User To Paid TabBarController - FULL Access:
let vc = self.storyboard!.instantiateViewControllerWithIdentifier("TabBarControllerPaid") as! TabBarControllerPaid
self.presentViewController(vc, animated: true, completion: nil)
}
else if (!defaults.boolForKey("purchased")){
print("user has NOT purchased")
// Send User To Free TabBarController - PARTIAL Access:
let vc = self.storyboard!.instantiateViewControllerWithIdentifier("TabBarControllerFree") as! TabBarControllerFree
self.presentViewController(vc, animated: true, completion: nil)
}
}
At present, the user is sent to the first tab within the respective Tab Bar Controllers by default (index 0 or the left-most tab).
I would like to have the option of sending the user to the second tab (index 1) or third tab (index 2).
I realise I have to effect "SelectedIndex = 1" or "SelectedIndex = 2" into my code.
How can I do this please?
** ADDITIONAL INFO **
This is a snapshot of my storyboard map:
Basically, I have three (3) buttons (green colour) on my initial VC (the landing page).
(1) If the user clicks 'Enter', I check if they have full access or not, and send them to the respective Paid or Free Tab Bar Controller (red arrows).
(2) If the user clicks 'Workouts' I send them to the same Tab Bar Controllers in the same manner as the first/left most (index 0) tabs on both are 'Workouts' (yellow arrows).
(3) If the user clicks 'Exercises' I would like to send them to the second left most tab (index 1) in respective Paid or Free Tab Bar Controllers (blue arrows).
It is button three (3) for 'Exercises' that I am struggling to execute effectively.
In order for this to work, you have to change some settings in Xcode. Select the target. Go to the General tab. Where it says Main Interface, make it blank.
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
// Add this
var mainViewController: UITabBarController?
var isPaid: Bool {
get {
let defaults = NSUserDefaults.standardUserDefaults()
let saveIt = defaults.objectForKey("isPaid") as? Bool ?? false
return saveIt
}
set(newBool) {
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(newBool, forKey: "isPaid")
}
}
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if isPaid {
mainViewController = mainStoryboard.instantiateViewControllerWithIdentifier("TabControllerPaid") as? UITabBarController
}
else {
mainViewController = mainStoryboard.instantiateViewControllerWithIdentifier("TabControllerFree") as? UITabBarController
}
mainViewController?.selectedIndex = 0
self.window?.rootViewController = mainViewController
self.window?.makeKeyAndVisible()
}
// .......
}

Resources