is there an easy way to logout when clicking the back nav item on a navigation controller in swift 4? - swift

I've got a login screen that takes an email and password. I'm using firebase Auth.
The login screen is embedded in a navigation controller. From the login screen it goes to a UserDetailsController. There is a "back" nav item in the navbar that comes with the nav controller. I can't actually drag this to be an outlet.
I was wondering if there is an easy way for when the "back" is clicked and the user is returned to the login page to logout the user. The code for logout is relatively simple with firebase Auth. The issue i'm having is working out in LoginController if I returned to here from the UserDetailsController.
I've read up about using self.presentingcontroller to determine which controller i came back from but I keep getting nil. And I wasn't sure if this is the best/only option to determine from which controller I have navigated back from.
Thanks.

Looks pretty straightforward. Hopefully this code will help.
In viewDidLoad add a function like this:
override func viewDidLoad() {
super.viewDidLoad()
setupLogOut()
}
Then set up the function like this with a selector handler method like this:
fileprivate func setupLogOut() {
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(handleLogOut))
}
#objc func handleLogOut() {
do {
try Auth.auth().signOut()
// Present the login controller
let loginController = LoginController()
let navController = UINavigationController(rootViewController: loginController)
self.present(navController, animated: true, completion: nil)
} catch let signOutErr {
print("Failed to sign out:", signOutErr)
}
}

If you don't want to add method to back button. you can also use below method to logout:
override func viewWillDisappear(animated: Bool) {
// Your sign out logic here..
}
Above method get's called when you pop back to login controller.

Related

iOS 13 Modals - Calling swipe dismissal programmatically

I'd like to detect a modal dismissal in the view controller that's presenting the modal.
This method works amazing for detecting the new iOS 13 swipe dismissal on the new card modals:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "MyIdentifier" {
segue.destination.presentationController?.delegate = self
}
}
extension MyController: UIAdaptivePresentationControllerDelegate {
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
//clean up UI (de-selecting stuff) once modal has been dismissed
}
}
However, presentationControllerDidDismiss is NOT called if the modal dismisses itself programmatically through an action:
#IBAction func btnDismissTap(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
Is this a bug or is there a way I can programmatically call whatever the "swipe" dismiss is so I can detect all dismissals the same way? Currently I'm writing extra "dismiss" delegate methods into my modals as a work around and it seems unnecessary.
However, presentationControllerDidDismiss is NOT called if the modal dismisses itself programmatically through an action
self.dismiss(animated: true, completion: nil)
It doesn’t need to be called, because you dismissed the modal yourself, in code. You cannot not know that the modal was dismissed. You don’t need to receive a dismissal signal, because you gave the dismissal signal in the first place.
You typically don’t get a delegate method call reporting something your own code did. Delegate methods report user actions. It would be crazy if everything you yourself did in code came back as a delegate method call.
Mojtaba Hosseini, answer is something I was looking for.
Currently, I need to write a delegate function to let the presenting view know that the user dismissed the modal PLUS do the presentationControllerDidDismiss handler for swipe dismissals:
#IBAction func btnDismissTap(_ sender: Any) {
self.dismiss(animated: true, completion: {
self.delegate?.myModalViewDidDismiss()
})
}
I wanted to handle both of these the same way and Mojtaba's answer works for me. However, presentationControllerDidDismiss does not get invoked if you call it inside of the self.dismiss completion block, you need to call it before.
I adapted my code to use "presentationControllerWillDismiss" (for clarity) and simply called the delegate before I dismiss programmatically in my modals and it works great.
#IBAction func btnDismissTap(_ sender: Any) {
if let pvc = self.presentationController {
pvc.delegate?.presentationControllerWillDismiss?(pvc)
}
self.dismiss(animated: true, completion: nil)
}
Now, I no longer need to create delegate functions to handle modal dismissals in code and my swipe handler takes care of all scenarios.
FYI, what I'm "handling" is doing some UI clean up (de-selections, etc) on the presenting UI once the modal is dismissed.
As #matt mentioned, there is no need to inform the one who dismissed a view by delegate. Because it is already known. BUT if you need that delegate method to be called, you should call it manually your self after dismissing the view:
#IBAction func btnDismissTap(_ sender: Any) {
self.dismiss(animated: true) {
presentationController?.delegate?.presentationControllerDidDismiss?(presentationController!)
}
}

How to invoke a method from a modal view controller class in Swift?

Basically for this simple game app I have 2 different UIViewControllers called ViewController and PreviewController. PreviewController is opening view with the title screen and a label titled "Start game". When the label is tapped, it initiates a modal view controller (the ViewController class that has all the views for the actual game itself) and calls the "EnterNewGame" method from ViewController that sets up the game. Right now the issue I have is when calling this method, only part of the method seems to be running.
Here is the function in PreviewController that is being initiated upon tap:
#objc func handleButtonTap(_ recognizer: UITapGestureRecognizer) {
self.present(ViewController(), animated: true, completion: {() -> Void in
ViewController().enterNewGame()
})
}
And here is the EnterNewGame() method from ViewController
func enterNewGame() {
//show suit indicators when starting a new game
bluePlayerSuitsHidden = false
redPlayerSuitsHidden = false
game.blueTurn = true
self.setBackground()
self.cleanUpBoard()
self.createBoard()
self.displayBoard()
self.setSuitIndicators()
self.highlightCards()
playButton.isEnabled = false
}
Right now, when the label is tapped the screen transitions to the modal view controller but only displays a black screen with only one of the game setups (setting a few images on the top of the screen) working properly. I am sure that the EnterNewGame method works properly to actually start the game because I have tested it in isolation, so I think I am just not setting up the modal view controller properly or I have to call the method differently. Any help is appreciated, thanks.
Controller on which you're calling your method ins't the same instance as controller which you're presenting, you need constant (also your code can be simplified by avoiding using self references and writing name of completion parameter with specifing closure's parameter and return type)
#objc func handleButtonTap(_ recognizer: UITapGestureRecognizer) {
let controller = ViewController()
present(controller, animated: true) {
controller.enterNewGame()
}
}
Also, you can call this method on some other method inside your certain controller like viewDidLoad, viewWillAppear or you can create factory method which would return you certain set controller.
This last part leads me to idea: look how you instantiate your controller and look carefully if you don't need to instantiate it through storyboard or nib file.
class ViewController: UIViewController {
class func instantiate() -> ViewController {
let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Identifier") as! ViewController
// let controller = ViewController(nibName: "ViewController", bundle: nil)
controller.enterNewGame()
return controller
}
}
Usage:
#objc func handleButtonTap(_ recognizer: UITapGestureRecognizer) {
present(ViewController.instantiate(), animated: true)
}

How i can go away from closure in viewcontroller?

try to find some info about it, but i can't.
So i got this code when i clicking on login.
#IBAction func checkLogin(_ sender: UIButton) {
let email = userEmail.text!
let password = userPassword.text!
if email == "" || password == "" || !email.isValidEMail {
userEmail.shake()
userPassword.shake()
return
}
Auth.auth().signIn(withEmail: email, password: password, completion: {(user, error) in
var collectError = ("","")
if error != nil {
collectError = ("Error", (error?.localizedDescription)!)
self.alertMsg(title: collectError.0, message: collectError.1)
return
}
if !(user?.isEmailVerified)! {
collectError = ("Error", "Your account is not verified! Confirm your account from email!")
return
}
print("OK, User Authentificated: \(Auth.auth().currentUser?.email)")
// DispatchQueue.main.async { }
//return
self.navigationController?.popViewController(animated: true)
//self.dismiss(animated: true, completion: nil)
})
i have first view controller which gives a user a login or registration options. So i choose for example login. try to login and it returns me back to first view controller there i got verification firebase listener if user loggen in it hides first view controller but when i click to logout in profile.storyboard it immeditialy show me first login controller. and when i try to login again again nothing happens why i dont know
i understand that i need use for navigation return back function
self.navigationController?.popViewController(animated: true)
and for buttons if i want hide viewcontrol
self.dismiss(animated: true, completion: nil)
So, when your root controller of the main.storyboard is loaded or will/did appear, you are trying to check the authorization, if user is not authorized, you will present the login controller, right?
In your case you need to have at least two separated navigation stacks (let's call them the main stack and the login stack)
It means that your login view controller should be presented (not pushed) when user is not authorized and dismissed (not popped) after user will be logged successfully or the reg will be canceled.
I have almost the same reg process in my project. I will try to explain it in the example code below
class MainController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
//detecting the firebase, just a custom bool var
if !isAuthorized {
//call the method to present login controller
presentLoginController()
}
}
func presentLoginController(){
let segue = UIStoryboard(name: "Login", bundle: nil)
if let loginNavigationController = segue.instantiateInitialViewController() {
self.present(loginNavigationController, animated: true, completion: nil)
}
}
}
Now you will see the login controller. So this will make a second navigation stack (login stack). This will be a separated navigation, where the root controller is a login navigation controller (initial controller in you Login.storyboard). For any controller in this stack if you will call navigationController?.popToRootViewController it will bring you to the root controller in the login stack. Don't use self.dismiss(animated: true, completion: nil) here, likely it will break your navigation
So when user is logged in or authorization was finished or canceled, you need to show a main controller (the main stack), where you can make that check for isAuthorized again. This is how you can make it:
func navigateToTheRoot(){
//this code will dismiss all modal controllers that was presented
UIApplication.shared.keyWindow?.rootViewController?.dismiss(animated: false, completion: nil)
//accessing to the appDelegate
let appDelegate = UIApplication.shared.delegate as? AppDelegate
//now this will return your app to the main controller
if let navigationController = appDelegate?.window?.rootViewController as? UINavigationController {
navigationController.popToRootViewController(animated: true)
}
}
Call this method in any controller that is presented in separated navigation stack (like to cancel the registration, call this after clicking the "X" button in your login stack).
If your profile controller has a navigation controller as an initial of the Profile.storyboard (third stack - the profile stack), then you need to present it in the same way as I've used for the login controller. In this case, to make the logout, you need to call method navigateToTheRoot() from any controller in the profile stack to show the main controller.
Otherwise, if your profile controller is just a single controller without additional navigation and it was pushed from any other controller from your main stack, then you just need to call this:
self.navigationController?.popToRootViewController(animated: true)
You will see the root controller of your main.stack
Short rules to get to the root in the Main.storyboard from any other controller no matter how many controllers had been presented or pushed before:
if you controller is presented (not in the main stack) and it is in an additional navigation stack, use navigateToTheRoot() method I've described
if your controller was pushed (push segues or navigationController?.pushViewController for programmatically usage) use navigationController?.popToRootViewController
if your controller is presented (is Modal) and it is just single controller without a navigation, just call self.dismiss(animated: true, completion: nil)
Hope it will help you. You can ask me if you need
P.S. In your registration controller (showing when user clicks "Зарегистрироваться") there is a small mistake "Зарегестрироваться". Удачи! ;)

Swift Hide Nav Bar on One Page

I have an app that opens to the main scene and checks for a token to see if the user is logged in. If they are all is good and it loads. If not it redirects them to a login screen, they login and then it pushes them back to the main screen.
I'm trying to hide the nav bar just on the login page which loads if there's no token. I've found many instances of code that work, like this:
self.navigationController.navigationBar.hidden = true
that hide it however they also hide the nav bar on the first instance of the main page. How can I avoid this?
EDIT: I've tried the following and still no luck! Same result of the navbar hiding on the login screen but hidden when the user is first moved to the main scene. Any other move to the main screen shows the navbar. How is this happening??
Attempt 1:
ON LOGIN PAGE
In viewDidLoad() -> self.navigationController!.navigationBar.hidden = true
ON MAIN PAGE
In viewDidLoad() -> self.navigationController!.navigationBar.hidden = false
Attempt 2
ON LOGIN PAGE
override func viewWillAppear(animated: Bool) {
self.navigationController!.navigationBarHidden = true
}
ON MAIN PAGE
override func viewWillAppear(animated: Bool) {
self.navigationController!.navigationBarHidden = false
}
Attempt 3
ON LOGIN PAGE
override func viewWillAppear(animated: Bool) {
self.navigationController!.navigationBarHidden = true
}
ON MAIN PAGE
override func viewWillDisappear(animated: Bool) {
self.navigationController!.navigationBarHidden = false
}
Could it have something to do with the way I'm moving to the main page after the user logs in?
ON LOGIN PAGE:
let secondViewController = self.storyboard!.instantiateViewControllerWithIdentifier("FriendsTableViewController") as! UITableViewController
//go to the main page
self.navigationController!.pushViewController(secondViewController, animated: true)
in viewWillDissapear you can set self.navigationController.navigationBar.hidden to false again.
Set it to hidden in viewDidAppear. I had to use something like this in my application and it worked.
Override func viewWillAppear(){ Self.navigationController.navigationBar.hidden true
}
Try this:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
/** remove the shadow image (border) altogether from nav bar **/
for parent in self.navigationController!.navigationBar.subviews {
for childView in parent.subviews {
if(childView is UIImageView) {
childView.removeFromSuperview()
}
}
}
}

PerformSegue in StoryBoard

I am working for the first time on storyboard using Swift.
In the first page of the app user will login and when login is done successfully, user will navigate to next page.
But When button is clicked firstly navigation occurs then after web service gets called, but what I want is to authenticate the Login Web service first then navigate with login data to next page.
I have put the identifier on the login button i.e, "login_success" on the button in storyboard and called self.performSegueWithIdentifier, when login is successfull.
Please guide me. Thanks.
You can add segue in the identifier in storyboard. Set the segue in the storyboard from File Owner. And when we get the response from the server, then using,
dispatch_async(dispatch_get_main_queue(), {
if self.loginInfo.userEmail.length > 0{
self.performSegueWithIdentifier("login_success", sender: self)
}
})
Here, I am checking whether response string has value or not.
Then call the method of segue to navigate to next page.
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!)
{
if segue.identifier == "login_success"
{
var mainVC: MainViewController!
mainVC = segue.destinationViewController as MainViewController
mainVC.mainLoginInfo = loginInfo
}
}
MainViewController is the page to which I want to move after login is done successfully.