How i can go away from closure in viewcontroller? - swift

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 "Зарегестрироваться". Удачи! ;)

Related

Get destination view controller when pressing back on navigation bar

When we perform a segue, it is easy to get the destination view controller so we can pass data to it using the prepare(for:) method.
I'd like to know the correct way to do this when the back button of a navigation controller is pressed.
I've managed to piece together something that works, but it feels wrong to be using my knowledge of the hierarchy of the view controllers within the navigation controller rather than getting the destination dynamically. Maybe i'm overthinking it?
override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent: parent)
// This method is called more than once - parent is only nil when back button is pressed
if (parent == nil) {
guard let destination = self.navigationController?.viewControllers.first as? MyTableViewController else {
return
}
print("destination is \(destination)")
// set delegate of my networking class to destination here
// call async method on networking class to retrieve updated data for my table view
}
}
As a general rule, trying to do that makes for too tight of a coupling. The VC you are navigating away from shouldn't need to know about where it's headed.
Probably better to implement your networking class methods in viewWillAppear or viewDidAppear inside your MyTableViewController class.
However, you could give this a try:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// make sure we're in a navigation controller
guard let navC = navigationController else { return }
if navC.viewControllers.firstIndex(of: self) != nil {
// we're still in the nav stack, so we've either
// pushed to another VC, or presented a full-screen VC
// (or maybe something else)
return
}
// we've been removed from the nav stack
// so user tapped Back button
print("back button tapped")
// see if we're navigating back to an instance of MyTableViewController
guard let destVC = navC.viewControllers.last as? MyTableViewController else {
return
}
// do something with destVC...
print("on our way to", destVC)
}
Note: I just did this as an example... it would need plenty of testing and possibly additional case handling.

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

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.

How to be notified when a UINavigation is dismissed using protocols?

I'm working on a simple UI Kit based game using swift, some of the pages will fire a modal or another page; when this page is complete it returns to the calling page.
What I'd like to know is how do I notify or watch or otherwise listen for it's return so I can do some actions.
For example.
Game has 3 players
Page with modal dialog. A user does an action on this modal
Dialog is dismissed and returns with some changes
The launching page now moves to the next player on turn, or if there are no more players on turn; go to the next segue.
I believe its possible to do it using Protocols?
So what I'm wanting is to listen for the UINavigation to return to my launching page and do some actions.
But how do I do this using Swift?
Thanks
To do this you can use closures in swift.
Refer to : https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html
When you are presenting the second controller, you can set a closure as a property of second controller. Now when you dismiss the second controller, you can call this closure in second controller's dismiss block.
Example:
class FirstViewController: UIViewController
{
func presentSecondController()
{
let secondController = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
secondController.completionClosure = {
//Write your code here that you want to execute on FirstViewController when secondController is dismissed
}
self.present(secondController, animated: true, completion: nil)
}
}
class SecondViewController: UIViewController
{
var completionClosure : (()->())?
func dismissController()
{
self.dismiss(animated: true) {
if let closure = self.completionClosure
{
closure()
}
}
}
}

Swift - Access different View Controllers in App Delegate

In app delegate, with a simple app having only 2 screens:
first screen is a Table View Controller embedded in Navigation Controller
second screen is a View Controller which is used to add items to the first screen table via protocol/delegate/segue
And this is the code for didFinishLaunchingWithOptions in app delegate that I can reference the first screen as viewController[0]:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let navController = self.window?.rootViewController as! UINavigationController
let courseListController = navController.viewControllers[0] as! CourseListController
courseListController.managedObjectContext = self.managedObjectContext
return true
}
How can I reference the screens at above, below and next to the center sreen? Please suggest me a solution. Thank you!
It's important to keep in mind that the view controller objects for all of the "peripheral" view controllers in your story board won't actually exist until the segue to get to them is executed, so there's no way to get access to them directly from the app delegate. Instead, you need to push state to each child view controller as it's created, from whatever the source view controller is. Segues are the appropriate way to do this.
You will probably want to assign each of the segues from the central view controller a unique segue identifier in Interface Builder. Click on the segue, then enter it here:
In the central view controller, implement prepareForSegue(_:sender:), doing something like the following:
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject) {
switch segue.identifier {
case "SegueIdentifier1":
guard let destination = segue.destinationViewController as? ViewController1 else {
return
}
// set up your view controller here
case "SegueIdentifier2":
guard let destination = segue.destinationViewController as? ViewController2 else {
return
}
// set up your view controller here
// add additional segues as required
default:
break // unknown segue
}
}
Go to each view Controller you want to reference and in the identity inspector, add some string to its StoryBoard ID.
next to reference it from the new ViewController (say, XViewController) to (say, YViewController)
do this :
var referencedViewController = self?.storyboard.
instantiateViewControllerWithIdentifier("referenceViewID") as! YViewController
self.presentViewController(referencedViewController,
animated: true, completion: nil)

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.