Why navigation controller is nil even though I push it - ios

I have a LoginViewController and want to push MainTabBarController
So, I do this
let controller = MainTabBarController()
navigationController?.pushViewController(controller, animated: true)
LoginViewController has a navigation controller but MainTabBarController doesn't for some reasons.
I have two questions, if my flow is LoginViewController -> MainTabBarController -> Other Controllers embedded in MainTabBarController, and I want to log out, how do I get to the LoginViewController without memory leaks? I was thinking about something like popToRootViewController but not sure if it would work.
This is how I'm creating other controllers in MainTabBarController
private func createNewViewController(viewController: UIViewController, title: String, imageName: String) -> UIViewController {
navigationController?.navigationBar.prefersLargeTitles = true
navigationController?.tabBarItem = UITabBarItem(title: title, image: UIImage(systemName: imageName), selectedImage: nil)
navigationController?.view.backgroundColor = .white
viewController.navigationItem.title = title
return viewController
}
But it doesn't really work since navigationController is nil. I don't use storyboards at all though.
EDIT: Apparently embedding a UITabBarController inside a UINavigationController is not supported.

Try this design. When LoginVC appears it checks if user is logged in and re-directs to MainVC. This is just a skelton, add more details as you go forward:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
window = UIWindow()
window?.makeKeyAndVisible()
window?.rootViewController = LoginVC()
return true
}
}
class LoginVC: UIViewController {
var isUserLoggedIn = true
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
view.backgroundColor = .red
if isUserLoggedIn {
present(MainVC(), animated: true)
}
}
}
class MainVC: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
modalPresentationStyle = .fullScreen
let firstVC = UIViewController()
firstVC.tabBarItem = UITabBarItem(title: "firstVC", image: nil, selectedImage: nil)
let secondVC = UIViewController()
secondVC.tabBarItem = UITabBarItem(title: "secondVC", image: nil, selectedImage: nil)
viewControllers = [firstVC, secondVC]
}
}

Related

Get custom NavigationController class from custom UIViewController

I am trying to implement the side navigation menu (one that we have in Android) in iOS App.So far i tried the following:-
AppDelegate.swift :-
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var bridge: RCTBridge!
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions:
[UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let sb = UIStoryboard(name: "mystoryboard", bundle: Bundle.main)
let rootViewController: UIViewController =
sb.instantiateViewController(withIdentifier:
"NativeLabelSwiftViewController") as UIViewController
let navigationController = UINavigationController(rootViewController:
rootViewController)
window = UIWindow(frame: UIScreen.main.bounds)
window!.rootViewController = navigationController
window!.makeKeyAndVisible()
return true
}
The above is my AppDelegate class since it is a React Native project we have the generated code as well.Here added the following code to instantiate my rootViewClass(NativeLabelSwift) by following code :-
let sb = UIStoryboard(name: "mystoryboard", bundle: Bundle.main)
let rootViewController: UIViewController =
sb.instantiateViewController(withIdentifier:
"NativeLabelSwiftViewController") as UIViewController
NavigationLabelSwiftViewController.swift(Custom View Class) :-
class NativeLabelSwiftViewController: UIViewController{
var bridge: RCTBridge!
override func viewDidLoad() {
super.viewDidLoad()
title = "Production Tracking"
let btn2 = UIButton(type: .custom)
btn2.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
btn2.addTarget(self,
action:#selector(NativeLabelSwiftViewController.
onBtn2Clicked.(_:)), for: .touchUpInside)
let item2 = UIBarButtonItem(customView: btn2)
self.navigationItem.setLeftBarButton(item2, animated: true)
}
#IBAction func onBtn2Clicked(_ sender: UIBarButtonItem) {
if let navViewController = self.navigationController as?
NavigationController {
// navigation view controller is available
}
else{
// navigation view controller not available
}
}
}
The Navigation Controller has a UIViewController class (NativeLabelSwiftViewController) that has a button in Tab Bar.On Press of the Button i need to access the Navigation Controller method.But i am not able to get the navigation controller using the below code :-
let navViewController = self.navigationController as?
NavigationController; //this is NIL
But
let uiViewController = self.navigationController as?
UINavigationController; //this is not NIL
You are not referring your custom navigation controller in the AppDelegate. You used UINavigationController(rootViewController: rootViewController).
You if you want to use the storyBoard NavigationController, In AppDelegate use -
let nav = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "NavigationController") as! NavigationController
window = UIWindow(frame: UIScreen.main.bounds)
window!.rootViewController = nav
window!.makeKeyAndVisible()
return true
Note: Don't forget to put an Identifier for the StoryBoard NavigationController.

Switching the ViewController with button doesn't shows the NavigationBar in second ViewController

I want to create a page were you press the "Login" button(witch is located on NavigationBar, left item) and after is showing the second page(aka home page) were user can sign out. In the same time I want to remember that the user is logged in, using UserDefault.
Here is my AppDelegate file:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
theViewController(controllerIs: LoginPage())
return true
}
fileprivate func theViewController(controllerIs: UIViewController) {
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
window?.rootViewController = UINavigationController(rootViewController: controllerIs)
}
}
The Login view:
import UIKit
class LoginPage: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Login", style: .plain, target: self, action: #selector(handleLogin))
}
#objc private func handleLogin() {
let root = UIApplication.shared.keyWindow?.rootViewController
root?.present(SignOutPage(), animated: true, completion: {
//some code here
})
}
}
The Sign Out view:
import UIKit
class SignOutPage: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .yellow
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Sign Out", style: .plain, target: self, action: #selector(handleSignOut))
}
#objc private func handleSignOut() {
let root = UIApplication.shared.keyWindow?.rootViewController
root?.present(LoginPage(), animated: true, completion: {
//some code here
})
}
}
The result is this:
and the sign out view
instead of
There are 2 things in going forward in navigation hierarchy.
Navigation Stack only follows in case of pushViewController. In case of present modally you need to initiate new navigation stack
let navVC = UINavigationController.init(rootViewController: viewController)
self.present(navVC, animated: true, completion: nil)
Where, viewController is the object of your new UIViewController.
Rather than using UIApplication.shared.keyWindow?.rootViewController each time you should use navigation stack( for push) and view controller( for present) as:
// Push
self.navigationController?.pushViewController(viewController, animated: true)
// Present Modally
self.present(viewController, animated: true, completion: nil)
Where, self is your view controller's object and viewController is object of new UIViewController.
So, your handleLogin() on LoginPage will be like:
// Push
#objc private func handleLogin() {
guard let vc = UIStoryboard.init(name: "Main", bundle: .main).instantiateViewController(withIdentifier: "SignOutVC") as? SignOutPage else { return }
self.navigationController?.pushViewController(vc, animated: true)
}
// Pop
#objc private func handleLogin() {
guard let vc = UIStoryboard.init(name: "Main", bundle: .main).instantiateViewController(withIdentifier: "SignOutVC") as? SignOutPage else { return }
let navVC = UINavigationController.init(rootViewController: vc)
self.present(navVC, animated: true, completion: nil)
}
And, handleSignOut() on SignOutPage will be like:
// Push
#objc private func handleSignOut() {
self.navigationController?.popViewController(animated: true)
}
// Pop
#objc private func handleSignOut() {
self.dismiss(animated: true, completion: nil)
}
NOTE: You need to set storyboard-identifier as:
When you do this:
let root = UIApplication.shared.keyWindow?.rootViewController
root?.present(SignOutPage(), animated: true, completion: {
//some code here
})
you are presenting the SignOutPage view controller modally which will put it over the root view controller.
What you are wanting to do is to push it onto the navigate controller stack so you need something like this:
navigationController?.pushViewController(SignOutPage(), animated: true)
In the same way when you sign out instead of presenting the view controller you want to pop the current one off the stack doing something like this:
navigationController?.popViewController(animated: true)
That will return the app back to the previous view controller (the login one).

can't change title navigation Controller

I can't change the title of my navigationBar in the options view. I am not using Storyboards. I can't add button too.
This is my App Delegate code :
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let viewController = MenuTableViewController(nibName: nil, bundle: nil) //ViewController = Name of your controller
let navigationController = UINavigationController(rootViewController: viewController)
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = navigationController
self.window?.makeKeyAndVisible()
return true
}
This is my MenuTabeViewController file
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Options", style: .plain, target: self, action: #selector(handleOptions))
}
func handleOptions() {
let optionViewController = optionsViewController()
present(optionViewController, animated: true, completion: nil)
}
this is my options file
class optionsViewController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
view?.backgroundColor = UIColor.white
//I've tried 3 solutions
self.navigationItem.title = "Options"
self.title = "Options"
self.navigationBar.topItem?.title = "Options"
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
dismiss(animated: true, completion: nil)
}
}
I believe the issue is due to the fact that you're not setting up the new navigation / view controller properly when presenting it.
When presenting your new UINavigationController, as you did previously in the AppDelegate, you'll want to create a UIViewController and set it as the rootViewController. So you'll want something like OptionsNavController and OptionsViewController instead of a single nav controller.
Then in your OptionsViewController simple call self.title = #"Options".
EDIT
I'm including an example below.
func handleOptions() {
let optionsViewController = OptionsViewController()
let optionsNavController = UINavigationController(rootViewController: optionsViewController)
present(optionsNavController, animated: true, completion: nil)
}
so then as stated above, call self.title = #"Options" in OptionsViewController.

How to switch between uiViews from a non UIView class

So here's the situation. I have an app which presents a tabBarController as its root view controller, with a number of tabItems. So here's the setup in my app delegate
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let vc1 = VC1(nibName: "VC1", bundle: nil)
let vc2 = VC2(nibName: "VC2", bundle: nil)
let menuTabBarController = MenuTabBarController(nibName: "MenuTabBarController", bundle: nil)
menuTabBarController.viewControllers = [VC1,VC2]
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window?.rootViewController = menuTabBarController
window?.makeKeyAndVisible()
//Setup tab bar
UITabBar.appearance().barTintColor = UIColor.blackColor()
let IMG1 = UIImage(named: "img2.png")
let IMG2 = UIImage(named: "img2.png")
VC1.tabBarItem = UITabBarItem(
title: "View 1",
image: IMG1,
tag: 1)
VC2.tabBarItem = UITabBarItem(
title: "View 2",
image: IMG2,
tag:2)
return true
}
func applicationWillResignActive(application: UIApplication) {
}
func applicationDidEnterBackground(application: UIApplication) {
}
}
So notice I have two UIViewController classes(VC1 and VC2) nested within the menuTabBar. Now from menuTabBar class, I can easily switch between the views like so
class MenuTabBarController: UITabBarController,UITabBarControllerDelegate{
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self//Set tabbarcontroller delegate
}
// UITabBarControllerDelegate
func tabBarController(tabBarController: UITabBarController, didSelectViewController viewController: UIViewController) {
tabBarController.selectedIndex = 1;//Say I wanted to transition to VC2
}
}
But What I ultimately want to do is to be able to switch between the two UIViewControllers from another class that is not a UIViewController. I attempted something like this
class Actions(){
func someFunctionThatSwitchesToVC2(){
//So I create a reference to appDelegate which in turn, is supposed to reference the tab bar and switch between its child views
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let tabBarController: UITabBarController = (appDelegate.window?.rootViewController as? UITabBarController)!
tabBarController.selectedIndex = 1
}
}
When I run the someFucntionThatSwitchesToVC2, I get this error "fatal error: Index out of range". Obviously I'm not doing something right. Any ideas greatly appreciated
PS, When I do tabBarController.selectedIndex = 0, I don't get the error but nothing happen
I think you are doing typo while assigning ViewControllers. Change this line:
menuTabBarController.viewControllers = [VC1,VC2]
with below line:
menuTabBarController.viewControllers = [vc1,vc2]

Moving from one UIViewController to another in Swift programmatically

I have 2 ViewControllers. I don't have a storyboard. I want to move from the first view to another once everything is done in first viewController. What is the way to do it? It is something like this, the first view shows an image and in the background makes some API calls. After API call succeeds, I want it to move to the second ViewController(LoginActivityViewController). I tried calling this in the first ViewController:
var loginActivity = LoginActivityViewController()
self.navigationController.pushViewController(loginActivity, animated: true)
But, this did not work. How to do this?
Here is my application function in AppDelegate
func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.mainScreen().bounds)
let navigationController: UINavigationController = UINavigationController(rootViewController: RootViewController())
navigationController.setNavigationBarHidden(true, animated: false)
window!.rootViewController = navigationController
window!.makeKeyAndVisible()
return true
}
Here is my RootViewController's viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let image1 = UIImage(named: "Default.png")
let imageview = UIImageView(image: image1)
self.view.addSubview(imageview)
var loginActivity = LoginActivityViewController()
self.navigationController.pushViewController(loginActivity, animated: true)
}
this how your method should look with initing a navigation controller properly:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
window = UIWindow(frame: UIScreen.mainScreen().bounds)
let rootViewController: RootViewController = RootViewController(nibName: "RootViewController", bundle: nil)
let navigationController: UINavigationController = UINavigationController(rootViewController: rootViewController)
window!.rootViewController = navigationController
window!.makeKeyAndVisible()
return true
}
and now you are able to push new view controllers into the hierarchy.
update
if you don't want to show the navigation bar insert this line into the code above.
navigationController.setNavigationBarHidden(true, animated: false)
var next = self.storyboard?.instantiateViewControllerWithIdentifier("DashboardController") as! DashboardController
self.presentViewController(next, animated: true, completion: nil)
don't forget to set ViewController StoryBoard Id in StoryBoard -> identity inspector

Resources