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

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()
}
// .......
}

Related

Tab bar not showing on home page after login

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!!

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!

AddingViewcontroller manually into the stack swift 3

I am coding the sign out button in my app.This is the flow of my app.
SplashvwController -> secretCodeViewController ->
LoginviewController -> DashboardViewController
in this DashboardViewController I have the signOut button.
My app has single sign in facility once user logged, next time when he opens the app,
SplashvwController -> DashboardViewController
I want to prompt the user to Loginviewcontroller whenever he clicks the sign out button.
Question
When user going through path 1 I can simply do popviewcontroller to go back to the previous viewcontroller. But when user go though the 2nd path,
how can I add the Loginviewcontroller manually into my
viewcontrollers stack to perform the same operation?
How can I check whether the LoginviewController exists in my current Viewcontrollers stack?
Please help me
I think, the below code helps you,
for (var i = 0; i < self.navigationController?.viewControllers.count; i++) {
if(self.navigationController?.viewControllers[i].isKindOfClass(Loginviewcontroller) == true) {
self.navigationController?.popToViewController(self.navigationController!.viewControllers[i], animated: true)
break;
}
}
To add a ViewController Manually , check the code below..
let navigation = mainStoryboard.instantiateViewController(withIdentifier: "LoginViewController") as! LoginViewController
let nav = UINavigationController(rootViewController: navigation)
appdelegate.window!.rootViewController = nav
Set the storyboard reference identifier for a LoginViewController on the Main.Storyboard file.
Whenever you want to show the LoginViewController just call the function
func launchLoginView() {
if let loginVC: LoginViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("LoginStoryboardID") as? LoginViewController {
// .instantiatViewControllerWithIdentifier() returns AnyObject! this must be downcast to utilize it
// LoginStoryboardID is the reference id for login view controller.
self.presentViewController(loginVC, animated: true, completion: nil).
// OR
//UIApplication.shared.keyWindow?.rootViewController = loginVC
}
}
Landing screen based on the User login status.
In AppDelegte.swift
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let isLoggedAlready = //Get the login status
if isLoggedAlready == true {
if let dashBoardVC: DashboardViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("DashboardStoryboardID") as? DashboardViewController {
//Already logged in then directly launch dashboard vc.
//change the code based on your needs
window?.rootViewController = dasbBoardVC
}
}
// Otherwise let it go as flow 1
return true
}
}

Logout from TabBar

What is the best way to log out from a TabBar. I have an app which in one of its tabs has a log out option in a row. When I tab this row I want to go to my log in screen. The app launch the log in screen if you are not log in or the main screen (is a tab bar controller) if you are already log gin. I manage this in the appDelegate:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let defaults = NSUserDefaults.standardUserDefaults()
let notFisrtRun = defaults.boolForKey("notFirstRun")
// Chooses between login view and my list view if the user is already authenticated when the app is launched
if AuthToken.sharedInstance.isAuthenticated() && notFisrtRun {
let tabBarViewController = mainStoryboard.instantiateViewControllerWithIdentifier("TabBarViewController") as? UITabBarController
self.window?.rootViewController = tabBarViewController
} else {
let loginViewController = mainStoryboard.instantiateViewControllerWithIdentifier("LoginViewController") as? LoginViewController
self.window?.rootViewController = loginViewController
}
self.window?.makeKeyAndVisible()
return true
}
If I try a show segue to my log in screen, it appear with the navigation bar and tab bar.
If I try an unwind segue, it work in the case you are not already log in and the app start with log in screen.But in the cases the app start with the tab bar controller because you are already log in, it doesn't work.
It is pretty normal for apps to show login screens modally. Upon logout, Present loginViewController modally. If the user logs back in, dismiss the loginViewController.
You could also create a custom segue that replaces the rootViewController, This question can get you started on that.

Resources