RevealViewController not working after programmatically set a view in iOS / Swift - ios

First of all, let me introduce what I'm trying to do. Second... I'm new to Swift/iOS development.
What I'm trying to do is:
When I open the APP, it loads the LoginVC. When the user logs in, the TableSelectionVC loads. If the user selects a cell, it goes to the Home screen with the selected values. If the user taps on the bell (blue arrows) the app should go to AlarmVC.
It should work, but it doesn't. The AlarmVC says that self.revealViewController() is nil. BUT If I go to alarmVC through the menu option (red arrows) it loads normally and the Menu is shown. Also, if I choose the option in green, it goes to the TableSelectionVC and if I tap the icon, it goes to alarmVC and it doesn't crash. The problem is if I go from LoginVC to TableSelectionVC and tap on the icon, the it will crash.
I think it is the way that I'm setting the views, instantiating a controller and setting the window.rootViewController.
In my AppDelegate I have the following functions:
func changeRootViewControllerToSWRevealViewController () {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "SWRevealViewController")
controller.modalTransitionStyle = .flipHorizontal
if let window = self.window {
window.rootViewController = controller
}
}
func changeRootViewControllerToPlantSelectionVC () {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "navPlantSelectionVC")
controller.modalTransitionStyle = .flipHorizontal
if let window = self.window {
window.rootViewController = controller
}
}
When the user logs in the app, the following function is executed:
static func goToPlantSelection() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
//there should be an alert error
return
}
appDelegate.changeRootViewControllerToPlantSelectionVC()
}
After that, the PlantSelectionVC is shown and if the user taps on an cell the appDelegate.changeRootViewControllerToSWRevealViewController() is executed and the HomeVC is shown.
If the user taps on the icon, it tries to go to the AlarmVC but it crashes, like I said. I think I'm doing something wrong with binding the SWRevealViewController with LoginVC and TableSelectionVC.
The following code in AlarmVC tries to execute:
static func setupMenuToggle(button: UIBarButtonItem, viewController: UIViewController) {
button.target = viewController.revealViewController()
viewController.revealViewController().rearViewRevealWidth = viewController.view.bounds.size.width * 0.9
button.action = #selector(SWRevealViewController.revealToggle(_:))
}
But is shows the error in the third line: found nil while unwrapping an Optional value
Can anyone help ?

I fixed it. I set the login screen as sw_front for the swRevealViewController and after that, when the user logs in I would change the swRevealViewController front view controller with
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "navPlantSelectionVC")
self.revealViewController().setFront(controller, animated: true)
It is working now.

Related

How do you switch the root view controller of UINavigationController when signing out the user (Swift 5)

I am trying to make it so that my initial root view controller is the HomeViewController. Then I run a code to check if the user (I am using Firebase) is logged on already. If the user is not logged on, then the root view controller will change to the LoginViewController and modally present the Login/Register screens and then release those VCs once the user has signed in.
func authenticateUserConfigureView() {
if Auth.auth().currentUser == nil {
DispatchQueue.main.async {
let navController = UINavigationController(rootViewController: LoginViewController)
navController.navigationBar.barStyle = .default
self.present(navController, animated: true, completion: nil)
}
} else{
configureViewComponents()
}
}
it seems like if I write the code rootViewController: LoginViewController this error shows up
Cannot convert value of type 'LoginViewController.Type' to expected argument type 'UIViewController'
Alternatively, If I write LoginViewController() then although it seems like the user gets directed to the Login view controller, the simulator immediately crashes.
UPDATE
I tried using
let homeViewController = self.storyboard?.instantiateViewController(identifier: Constants.Storyboard.homeViewController) as? HomeViewController
view.window?.rootViewController = homeViewController
view.window?.makeKeyAndVisible()
but nothing seems to happen at all (though it seems to run). I am using the main storyboard to create my view controllers by the way.
It's simple to solve, you need pass an ViewController instance, you can get from your Login StoryBoard, this way:
let loginViewController = UIStoryboard (name: "LoginStoryboardName", bundle: Bundle.main).instantiateInitialViewController()
let navController = UINavigationController(rootViewController: loginViewController )
UIApplication.shared.keyWindow?.rootViewController = navController
To work, in Login storyboard, your viewController needs to be set to InitialViewController.
If your Storyboard already has a NavigationController, you can just pass the ViewController instance to the rootViewController, this way:
let viewController = UIStoryboard (name: "LoginStoryboardName", bundle: Bundle.main).instantiateInitialViewController()
UIApplication.shared.keyWindow?.rootViewController = viewController
Hope this helps.

Swipe Back Gesture After Login in Swift

In my application, after the user is successfully logged in via his email and password in the first storyboard (Auth.storyboard), he is directed to one of the ViewControllers in the second storyboard (Main.storyboard). The problem is that user is able to swipe back to the login screen in Auth.storyboard.
navigationController?.interactivePopGestureRecognizer?.isEnabled = false
I know that with the above code it is possible to disable this swipe back gesture but according to most people it is not recommended.
Therefore I wonder that is there a better solution to prevent swipe back gesture after a user logged in.
The best way is to present your login controller modally, then dismiss it to don't add this controller in your navigation stack.
If it's your initial controller, embed it in a different controller than the next one or remove it from navigation stack programmatically.
You have to remove the Login ViewController from the stack and you can also make your home view controller as a rootViewController:
var mainNavigationController:UINavigationController?
//After login success
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let homeInstance = mainStoryboard.instantiateViewControllerWithIdentifier("HomeVC")
mainNavigationController = UINavigationController(rootViewController: homeInstance)
mainNavigationController?.navigationBar.hidden = true
self.window?.rootViewController = mainNavigationController
//Appdelegate code didFinishLaunching
if getUserDefault("isUser") == "YES" {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let homeInstance = mainStoryboard.instantiateViewControllerWithIdentifier("HomeVC")
mainNavigationController = UINavigationController(rootViewController: homeInstance)
mainNavigationController?.navigationBar.hidden = true
self.window?.rootViewController = mainNavigationController
}
else {
mainNavigationController = window!.rootViewController as? UINavigationController
}
When the login button is clicked you must have pushed the view controller to another screen.
Try to set the view controller instead of push:
func Login(){
let control = storyboard!.instantiateViewController(withIdentifier: identifier)
navigationController?.setViewControllers([control], animated: true)
}
If you were pushing your new controller ,then instead of pushing try adding that to keywindow like this . You will not be redirected to previous window then.
let homeStoryBoard = UIStoryboard(name: "Main", bundle: nil)
let vc = homeStoryBoard.instantiateViewController(withIdentifier: "HomeViewController") as? HomeViewController
let appDelegate = UIApplication.shared.delegate as! AppDelegate
if let window = appDelegate.window {
window.rootViewController = vc
window.makeKeyAndVisible()
}

Issue while not using SlideMenuControllerSwift as the first viewcontroller

I am trying to use the third party SlideMenuControllerSwiftfor displaying a hamburger menu. I am able to display the hamburger menu properly when it is loaded in the very first viewcontroller that comes up. But in case I have a login screen for example and after the login screen, I navigate to another screen and when I try to show the slide menu in this screen, I am not able to do so properly.
In the example from github for SlideMenuControllerSwift, the required screens are loaded in the AppDelegate and called in the AppDelegate itself from didFinishLaunchingWithOptions. Following this idea, even if I call the method that is called in didFinishLaunchingWithOptions from the viewDidLoad of my screen of choice (where I want to display the hamburger menu), the hamburger menu is not showing up in that screen. I do get the logo of the hamburger menu displayed on top left but it is not clickable. Please help...:)
When user login try this.
SlideMenuOptions.contentViewDrag = true
SlideMenuOptions.leftViewWidth = self.view.frame.size.width * 0.75
let contentVC = self.storyboard?.instantiateViewController(withIdentifier: "HomeViewController") as! HomeViewController
let leftVC = self.storyboard?.instantiateViewController(withIdentifier: "LeftViewController") as! LeftViewController
let slideVC = SlideMenuController(mainViewController: contentVC, leftMenuViewController: leftVC)
slideVC.view.clipsToBounds = true
self.navigationController?.pushViewController(slideVC, animated: true)
Replace pushViewController with PresentViewController ,
let rootController = UIStoryboard.init(name:"Main", bundle: nil).instantiateViewController(withIdentifier:"HomeVC") as! HomeViewController
SlideMenuOptions.leftViewWidth = 280.0
SlideMenuOptions.contentViewScale = 1.0
let leftMenu = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "slidingMenuNavigationController") as! UINavigationController
let slideMenuController = SlideMenuController(mainViewController: rootController, leftMenuViewController: leftMenu)
present(slideMenuController, animated: true, completion: nil)

Presenting VC embedded in a NavController from AppDelegate

I'm trying to want to present/push a VC embedded in a NavController from AppDelegate. I previously used this code but somehow it's not working anymore:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let VC = storyboard.instantiateViewControllerWithIdentifier("PendingRequest") as! PendingRequestVC
let navController = UINavigationController.self(rootViewController: VC)
let rootViewController = UIApplication.sharedApplication().keyWindow!.rootViewController
rootViewController!.presentViewController(navController, animated: false, completion: nil)
Other codes open my desired VC but not within a navigation pane. Any guidance would be appreciated.
Calling from AppDelegate after user interacts with push notification.
Edit:
I'm able to present the right VC by using this code:
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if let viewController = mainStoryboard.instantiateViewControllerWithIdentifier("PendingNavController") as? UINavigationController {
if let yourViewController = viewController.topViewController as? PendingRequestVC {
//yourViewController.getRequestdata()
}
UIApplication.sharedApplication().keyWindow!.rootViewController = viewController;
}
But this code won't allow me to go back using the Close button on my NavBar.
My structure is as follow:
TabController -> NavController1 -> VC1 -> NavController1a -> VC1a
I'm trying to get to VC1a and be able to use the Closed button to go back to VC1
Add a UIButton in your presented ViewController's View. Following event will be performed by that button. You can dismiss your Navigation Controller this way
#IBAction func dismissViewController(sender: AnyObject) {
self.navigationController.dismissViewControllerAnimated(false, completion:nil);
}

iOS Login / Sign Out Implementation in Swift

I've been attempting to implement a Login / Logout Flow for an iOS app in swift. Here's my storyboard -
In the main view controller (which is the blue screen), I have the following code implemented to detect that if the user is already signed in, then to automatically take them to the table view controller -
override func viewDidAppear(animated: Bool) {
if PFUser.currentUser() != nil {
self.performSegueWithIdentifier("test", sender: self)
}
The issue is, that when i Sign In or Login through either one of the green screens, the Table View navigation bar appears different. The 'Sign Out' button appears properly when the user opens the app and is already logged in, however, logging in or signing in through the green screens, the navigation bar contains a '< Back' button.
Can someone explain how a login / logout flow needs to be implemented in storyboard and programatically in Swift. I've seen some Objective-C examples out there, but can't seem to find one in Swift. If anyone has a good example, it would be really helpful.
Swift 4
In LoginViewController's login button action
#IBAction func abtn_login(_ sender: Any) {
let appDel:AppDelegate = UIApplication.shared.delegate as! AppDelegate
let mainStoryBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let centerVC = mainStoryBoard.instantiateViewController(withIdentifier: "InitialScreenTabBarController") as! InitialScreenTabBarController
// setting the login status to true
UserDefaults.standard.set(true, forKey: "isUserLoggedIn")
UserDefaults.standard.synchronize()
appDel.window!.rootViewController = centerVC
appDel.window!.makeKeyAndVisible()
}
validate login according to your scenario.
In View Controller having the logout button.
#IBAction func abtn_logout(_ sender: Any) {
UserDefaults.standard.set(false, forKey: "isUserLoggedIn")
UserDefaults.standard.synchronize()
let loginVC = self.storyboard?.instantiateViewController(withIdentifier: "LoginViewController") as! LoginViewController
let appDel:AppDelegate = UIApplication.shared.delegate as! AppDelegate
appDel.window?.rootViewController = loginVC
}
In AppDelegate, didFinishLaunchingWithOptions
let userLoginStatus = UserDefaults.standard.bool(forKey: "isUserLoggedIn")
if(userLoginStatus)
{
let mainStoryBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let centerVC = mainStoryBoard.instantiateViewController(withIdentifier: "InitialScreenTabBarController") as! InitialScreenTabBarController
window!.rootViewController = centerVC
window!.makeKeyAndVisible()
}
The < Back button is appearing because you are doing a push segue from the login view controllers to the tab bar controller. A better flow for the app would be to have the tab bar controller be your initial view controller. Then, in its viewDidAppear method, check that the user is logged in. If the user isn't logged in, segue modally WITHOUT animation to your login view controller. This will all happen without the user noticing and will allow the storyboard setup you want
You're better off with a setup like the one here. The problem is that your login view resides in a navigation controller as well. Just bring it outside of that. The segue I've place in is a "Show"
Keep in mind that you can embed a Tab Bar controller in a view controller by going up to the menu bar: Editor -> Embed In -> Tab Bar Controller.
Then create a segue to the Tab Bar Controller and you're good to go.
But what if they're already logged in?
Then you want to save a Boolean flag to NSUserDefaults that knows if the user is logged in. Then recall that Bool in AppDelegate under the didFinishLaunchingWithOptions and applicationWillEnterForeground similar to this:
var isLoggedIn: Bool? // Get From user defaults
let loginViewController = storyboard.instantiateViewControllerWithIdentifier("Login") as! UIViewController
let homeViewController = storyboard.instantiateViewControllerWithIdentifier("HomeNav") as! UIViewController
if isLoggedIn {
self.window?.rootViewController = homeViewController
}
else {
self.window?.rootViewController = loginViewController
}
let mainStoryboardIpad : UIStoryboard = UIStoryboard(name: "Register", bundle: nil)
let initialViewController = mainStoryboardIpad.instantiateViewController(withIdentifier: "navForLanding") as! UINavigationController
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()

Resources