I have to change my root view controller according to login status.
I had change my AppDelegate file and create a MainNavigationController class which extends UINavigationController.
**PROBLEM :- **
When the defined root class is loaded it gives all outlet variables as nil.
CODE
class PostalViewController: UIViewController {
#IBOutlet weak var btn_currentLocation: UIButton!
#IBOutlet weak var btn_viewAccount: UIButton!
#IBOutlet weak var in_postCode: UITextField!
let locManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
// Here it gives a nil on btn_currentLocation
btn_currentLocation.layer.cornerRadius = 15.0
btn_currentLocation.layer.masksToBounds = true
btn_currentLocation.setButtonGradient(colorOne: UIColor(named: "lightBlue")!, colorTwo: UIColor(named: "gradient2")!)
btn_viewAccount.layer.cornerRadius = 15.0
btn_viewAccount.layer.masksToBounds = true
// Add search button in post code text field
let searchButton = UIButton(type: .custom)
searchButton.setImage(UIImage(named: "iconSearch"), for: .normal)
searchButton.frame = CGRect(x: CGFloat(in_postCode.frame.size.width - 25), y: CGFloat(5), width: CGFloat(25), height: CGFloat(25))
searchButton.addTarget(self, action: #selector(self.actionSearch), for: .touchUpInside)
in_postCode.rightView = searchButton
in_postCode.rightViewMode = .always
}
AppDelegate.swift (only that particular function)
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
window?.rootViewController = MainNavigationController()
return true
}
MainNavigationController.swift
class MainNavigationController : UINavigationController{
var isLogin : Bool?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
isLogin = UserDefaults.standard.bool(forKey: "isLogin")
print("IsLogin --->", isLogin!)
if (isLogin != nil && isLogin!){
perform(#selector(postalController), with: nil, afterDelay: 0.01)
}else{
perform(#selector(showHomeContoller), with: nil, afterDelay: 0.01)
}
}
#objc func showHomeContoller () {
let homepageController = HomePageViewController()
present(homepageController, animated: true, completion: nil)
}
#objc func postalController () {
let postalController = PostalViewController()
viewControllers = [postalController]
}
}
EDIT
I have add a screenshot below, I have to switch my root view between HomePage and Postal
I suppose you just want to show PostalViewController if user is logged. If user isn't logged you want to show just HomePageViewController. First delete these lines from app delegate, you don't need this:
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
window?.rootViewController = MainNavigationController()
Now delete the whole navigation controller class. You won't need it because you will do all of this in HomePageViewController. Now in HomePageViewController swift file add this to viewDidLoad()
isLogin = UserDefaults.standard.bool(forKey: "isLogin")
print("IsLogin --->", isLogin!)
if isLogin != nil {
performSegue(withIdentifier: "segueToPostal", sender: self)
}
In the end set segue from HomePageViewController to PostalViewController
and set its identifier as segueToPostal
If you don't want to let user navigate back from Postal view controller you can just embbed PostalViewController in new NavigationController. Then just set segue from HomePageViewController to this NavigationController and set identifier.
IBOulet is set when view controller is initialized with nib file.
So you need create the view controllers using the init method below
init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)
For example, if name of your xib file where you config your IBOultets the same as controllers name, you can just do next:
let postalController = PostalViewController(nibName: "PostalViewController", bundle: nil)
Here You will have controller with initialized IBOutlets.
For using story board you should init them from story board.
Try to ini controllers next way:
let sb = UIStoryboard(name: "MainStoryboard", bundle: nil)
let vc = sb.instantiateViewController(withIdentifier: "PostalViewController")
And you should set storyboard id in your story board for the controller, like on the screen below.
storyboard id
Also you can find more info how to init correctly view controllers from storyboard here
What is a StoryBoard ID and how can i use this?
Related
I am trying to create a framework (Login VC) which contains a view controller. I have successfully imported the framework and presented the VC, but the view is not showing. I have a print function in the imported viewDidLoad and it is printing. What am I missing?
Framework VC:
public class LoginVC: UIViewController {
#IBOutlet weak var button: UIButton! {
didSet {
button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
}
}
public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func viewDidLoad() {
super.viewDidLoad()
print("View Loaded") // Firing
}
#objc func buttonPressed() {
print("hello")
}
}
Framework VC Xib:
This is view debugger when I present the framework VC
-- Update: This is how I am showing the VC ---
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let homeViewController = LoginVC()
homeViewController.view.backgroundColor = UIColor.white
window!.rootViewController = homeViewController
window!.makeKeyAndVisible()
return true
}
-- Update --
Since many comments relate to the app delegate, I first present a general ViewController which then will present my login framework VC.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let homeViewController = ViewController()
homeViewController.view.backgroundColor = UIColor.white
window!.rootViewController = homeViewController
window!.makeKeyAndVisible()
return true
}
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton! {
didSet {
button.addTarget(self, action: #selector(presentNext), for: .touchUpInside)
}
}
#objc func presentNext() {
let loginVC = LoginVC()
present(loginVC, animated: true, completion: nil)
}
}
Now, when I present the login framework, all I get is a black screen.
-- Update --
I can change the background color of the view in viewdidLoad, but the xib views are not shown. Why is this?..
Frameworks with xibs connected to viewController require explicit loading from the bundle.
Usually when we create a cocoaTouch UIViewController with xib, this is handled for us. However, when using frameworks, it is not handled.
To solve this, I add load the xib in viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
// Frameworks require loading of the xib to connect xib to view controller
let bundle = Bundle(for: type(of: self))
bundle.loadNibNamed("viewControllerName", owner: self, options: nil)
}
You need to call your presentNext() after the ViewController has actually appeared -- not in viewDidLoad, and not even in viewWillAppear.
Like this:
class ViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
presentNext()
}
#objc func presentNext() {
let loginVC = LoginVC()
present(loginVC, animated: true, completion: nil)
}
}
Here's the working test project:
https://github.com/drewster99/SO_LoginVC
Also, maybe double-check that your ViewController.xib has the IBOutlet actually attached for the button. It's actually got to be that. Everything else looks good.
Here's what I've got:
AppDelegate:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let homeViewController = ViewController()
homeViewController.view.backgroundColor = UIColor.white
window!.rootViewController = homeViewController
window!.makeKeyAndVisible()
return true
}
}
ViewController.swift:
import UIKit
import LoginFrameworkThing
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton! {
didSet {
button.addTarget(self, action: #selector(presentNext), for: .touchUpInside)
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
#objc func presentNext() {
print("Presenting next...")
let loginVC = LoginVC()
present(loginVC, animated: true, completion: nil)
}
}
The button is connected in ViewController, and LoginVC (both .xib and .swift) exist in the framework.
I updated the sample project. Check the link.
My app structure:
firstView: UIViewController with UINavigationController -- secondView UITabBarController with several UIViewControllers
start app, firstView:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let vc = ViewController()
let navContr = UINavigationController(rootViewController: vc)
self.window? = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = navContr
self.window?.makeKeyAndVisible()
return true
}
In firtView I click button to open secondView:
let vc = MyTabController() // my UITabBarController
self.navigationController?.pushViewController(vc, animated: true)
start secondView:
class MyTabController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let gen = MyViewController()
let tabGen = UITabBarItem()
gen.tabBarItem = tabGen
tabGen.image = UIImage(named: "general")
tabGen.title = "Все вопросы"
viewControllers = [gen]
....
}
In my secondView I want set Title, set UISearchControlleer in every tab. But if I write in ViewController
navigationItem.title = "myTitle" nothing changes. I see button "Back" only.
Here's the screenshot
Set title like this in viewDidLoad() Method
self.title = "Your Title"
So currently I have my AppDelegate set to make my CustomTabBarController the root view controller:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
FIRApp.configure()
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window?.makeKeyAndVisible()
window?.rootViewController = CustomTabBarController()
return true
}
In my TabBarController.swift file I override the viewDidLoad function with:
override func viewDidLoad() {
super.viewDidLoad()
// Custom view controllers
let layout = UICollectionViewFlowLayout()
let friendsController = ContactsController(collectionViewLayout: layout)
let recentMessagesNavController = UINavigationController(rootViewController: friendsController)
recentMessagesNavController.tabBarItem.title = "DMs"
recentMessagesNavController.tabBarItem.image = UIImage(named: "")
viewControllers = [recentMessagesNavController, createDummyNavControllerWithTitle("Groups", imageName: ""), createDummyNavControllerWithTitle("Submit", imageName: ""), createDummyNavControllerWithTitle("Search", imageName: ""), createDummyNavControllerWithTitle("Other", imageName: "")]
}
Following that my ContactsController.swift file has this:
private let cellID = "cellID"
override func viewDidLoad() {
super.viewDidLoad()
// Navigation bar
navigationItem.title = "Direct Messages"
collectionView?.backgroundColor = UIColor.whiteColor()
collectionView?.alwaysBounceVertical = true
collectionView?.registerClass(MessageCell.self, forCellWithReuseIdentifier: cellID)
setupData()
// Fetch messages provided by the fetch controller
do {
try fetchedResultsController.performFetch()
} catch let err {
print(err)
}
}
I do not currently have a storyboard file as all of my views are created programmatically. I have been trying to add a login screen that will pop up and block the TabBarController if the user is not logged in but I can not figure out the best way to implement this without getting errors. At the moment my SignInViewController does have custom elements created programmatically. What is the method to go about solving my problem?
Edit for comments:
Currently my SignInViewController has this:
override func viewDidAppear(animated: Bool) {
if let user = FIRAuth.auth()?.currentUser {
self.signedIn(user)
}
collectionView?.registerClass(BaseCell.self, forCellWithReuseIdentifier: cellID)
collectionView?.backgroundColor = UIColor.blueColor()
collectionView?.alwaysBounceVertical = false
tabBarController?.tabBar.hidden = true
view.addSubview(loginContainerView)
view.addConstraintWithFormat("H:|[v0]|", views: loginContainerView)
view.addConstraintWithFormat("V:|[v0]|", views: loginContainerView)
setupInputComponents()
}
private let cellID = "cellID"
var bottomConstraint: NSLayoutConstraint?
private func setupInputComponents() {
let loginControls = UIView()
loginControls.backgroundColor = UIColor.yellowColor()
loginContainerView.addSubview(emailField)
loginContainerView.addSubview(passwordField)
loginContainerView.addSubview(loginButton)
loginContainerView.addSubview(signUpButton)
loginContainerView.addSubview(passwordResetButton)
loginContainerView.addConstraintWithFormat("H:|[v0]|", views: emailField)
loginContainerView.addConstraintWithFormat("V:|[v0]|", views: emailField)
loginContainerView.addConstraintWithFormat("H:|[v0]|", views: passwordField)
loginContainerView.addConstraintWithFormat("V:|[v0]|", views: passwordField)
loginContainerView.addConstraintWithFormat("H:|[v0]|", views: loginButton)
loginContainerView.addConstraintWithFormat("V:|[v0]|", views: loginButton)
loginContainerView.addConstraintWithFormat("H:|[v0]|", views: signUpButton)
loginContainerView.addConstraintWithFormat("V:|[v0]|", views: signUpButton)
loginContainerView.addConstraintWithFormat("H:|[v0]|", views: passwordResetButton)
loginContainerView.addConstraintWithFormat("V:|[v0]|", views: passwordResetButton)
}
I don't really like to replace rootViewController to other view controllers. I think it should stay as it is.
Why not make it like this?
yourTabBarController.presentViewController(loginViewController,
animated: animated,
completion: nil)
This will show loginViewController on the top of your tabBarController. You could show it when user is not logged. But when the user press login and the login succeeded, you can show the main screen by using:
loginViewController.dismissViewControllerAnimated(true)
The typical thing to do is to check if the user needs login in applicationDidFinish... and then to set the window.rootViewController to the login VC if they need it and the starting VC if they don't
On correct login, the app should set the window rootViewController to whatever you want to start the app.
The VC stack should be a clean representation of what you want on there -- so either a login flow or the app, but not both.
I'm rebuilding an app without storyboards and the part of it that I'm having the most trouble with is navigating view-to-view programatically. Few things are written out there which don't use storyboards, so finding an answer for this has been tough.
My problem is pretty simple. I have my ViewController and my SecondViewController and I want to push from the former to the latter.
In AppDelegate:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window?.backgroundColor = UIColor.whiteColor()
window?.rootViewController = ViewController()
window?.makeKeyAndVisible()
return true
}
Then in ViewController.swift:
class ViewController: UIViewController, AVAudioPlayerDelegate, UITextFieldDelegate {
override func viewDidLoad() {
super.viewDidLoad()
startFinishButton.setTitle("Begin", forState: .Normal)
startFinishButton.addTarget(self, action: "moveToSecondViewController", forControlEvents: .TouchUpInside)
view.addSubview <*> startFinishButton
}
func moveToSecondViewController(sender: UIButton) {
let vc = SecondViewController()
println(self.navigationController) // returns nil
self.navigationController?.pushViewController(vc, animated: true)
}
}
Printing self.navigationController returns nil. I've tried doing:
var navController = UINavigationController() when the ViewController class is created (but outside of ViewDidLoad, right under the class declaration) and done the push using the navController var but that hasn't worked.
I'm thinking maybe the solution is to create a navigation controller in App Delegate that the whole app would use, I guess as a global variable?
My hope is that this post can serve many others who are new to Swift and want to remove storyboards from their app.
Thanks for taking a look and for your help.
In Swift 3
Place this code inside didFinishLaunchingWithOptions method in AppDelegate class.
window = UIWindow(frame: UIScreen.main.bounds)
let mainController = MainViewController() as UIViewController
let navigationController = UINavigationController(rootViewController: mainController)
navigationController.navigationBar.isTranslucent = false
self.window?.rootViewController = navigationController
self.window?.makeKeyAndVisible()
In AppDelegate
var window: UIWindow?
var navController: UINavigationController?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
navController = UINavigationController()
var viewController: ViewController = ViewController()
self.navController!.pushViewController(viewController, animated: false)
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window!.rootViewController = navController
self.window!.backgroundColor = UIColor.whiteColor()
self.window!.makeKeyAndVisible()
return true
}
In ViewController
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.title = "FirstVC"
var startFinishButton = UIButton.buttonWithType(UIButtonType.System) as! UIButton
startFinishButton.frame = CGRectMake(100, 100, 100, 50)
startFinishButton.backgroundColor = UIColor.greenColor()
startFinishButton.setTitle("Test Button", forState: UIControlState.Normal)
startFinishButton.addTarget(self, action: "buttonAction:", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(startFinishButton)
}
func buttonAction(sender:UIButton!)
{
println("Button tapped")
let vc = SecondViewController()
self.navigationController?.pushViewController(vc, animated: true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
In Swift 5 and Xcode 13 there is a SceneDelegate along with the AppDelegate. So now to completely remove the storyboard from a project and embed the view controller in a navigation controller do the following:
Delete the actual storyboard in the Project Navigator
Select the project in the Project Navigator, select Target and then the General tab, then delete the storyboard from Main Interface
In the info.plist file delete the storyboard from:
Application Scene Manifest > Scene Configuration > Application Session Role > Item 0 (Default Configuration) > Storyboard Name
Then in the scene delegate change the scene function to look like this:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: windowScene)
let navigationController = UINavigationController(rootViewController: YourViewController())
window.rootViewController = navigationController
self.window = window
window.makeKeyAndVisible()
}
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