iOS Can't dismiss view controller - ios

I have issue with my app. Scenario is simple, after successfully account creation i wan't to dismiss current page or navigate to login page. My storyboard looks like this:
After successful account creation i having a popup with some info about it's ok, we send you verification email and after this popup i want to go to the page second from left - it's my main application page (now called "View Controller").
I tried dismiss window, but i have no effect there, it can only dismiss my popup window.
When i trying to redirect then i have issue with back button when is pressed,it lead to Sign Up page. There is some code:
// Create new user and send verification email
Auth.auth().createUser(withEmail: userEmail, password: userPassword) { user, error in if error == nil && user != nil {
self.sendVerificationMail();
self.displayAlertMessage(alertTitle: "Success", alertMessage: "Your account created successfully. We send you a verification email.");
// Redirect to View Controller
} else {
self.displayAlertMessage(alertTitle: "Unhandled error", alertMessage: "Undefined error #SignUpViewController_0002");
}
}
...
func displayAlertMessage(alertTitle: String, alertMessage:String, alertRedirection:String = ""){
let alert = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: UIAlertController.Style.alert);
let okAction = UIAlertAction(title:"Ok", style: UIAlertAction.Style.default, handler: nil);
alert.addAction(okAction);
self.present(alert, animated:true, completion:nil);
}
If i add this:
self.view.window!.rootViewController?.dismiss(animated: false, completion: nil)
After alert, it close only alert, before alert, it do nothing ( same as dismiss).

To dismiss and pop to main view you can use alert button action handler.
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: { (action) in
self.dismiss(animated: true, completion: {
self.navigationController?.popToRootViewController(animated: true)
})
}))
Or you can use the navigation to specific view controller using below lines.
for viewController in self.navigationController!.viewControllers {
if viewController.isKind(of: <Your_Main_ViewController_Class_Name>.self) {
self.navigationController?.popToViewController(viewController, animated: true)
break
}
}
Your_Main_ViewController_Class_Name is the view controller that within your navigation controller stack to which you need to navigate. (ie) main view
To blindly navigate to main view once alert popup displayed, you can use completion handler while present the alert.
self.present(alert, animated: true, completion: {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
self.navigationController?.popToRootViewController(animated: true)
}
})

well, you are using a navigation controller, for "present" a new view controller, you need to push it, for example.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("IDOFYOURVIEW") as CLASS_NAME_OFYOUR_VIEWCONTROLLER
navigationController?.pushViewController(vc, animated: true)
with the last code you can "present" (push) a new view controller
Now, if you want to make a other action when your press backbutton, try with this lines
override func viewDidLoad {
super.viewDidLoad()
self.navigationItem.hidesBackButton = true
let newBackButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.Bordered, target: self, action: "back:")
self.navigationItem.leftBarButtonItem = newBackButton
}
func back(sender: UIBarButtonItem) {
//in this part you can move to other view controller, examples
// Go back specific view in your navigation controller
for controller in self.navigationController!.viewControllers as Array {
if controller.isKind(of: NAMECLASS_OFYOUR_VIEWCONTROLLER.self) {
_ = self.navigationController!.popToViewController(controller, animated: true)
break
}
}
// Perform your custom actions
// ...
// Go back to the previous ViewController
self.navigationController?.popViewControllerAnimated(true)
}
Regards

Related

How to check conditions before performing navigation segue

I have a storyboard controlled app. On the register page, I want to send the user to the home page when pressing the register button. So i dragged a segue from the button to the home page. But then I cannot check conditions before the segue is performed. But if i create a segue and perform it programmatically, the home page comes over the register page, allowing the user to swipe back. Can someone tell me how to check conditions before that segue is performed, or not allow the user to go back to the register page if doing it programmatically. This is my storyboard. Main.storyboard
Xcode 12.0 Swift 5.0
First of all you need to connect "Register" button to an IBAction in your code;
In the IBAction you can call function:
func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil).
If in condition everything is succesfull you call this function otherwise return with error.
Example of sign out button:
private func showLoginViewController() {
// Creates the view controller with the specified identifier
let vc = storyboard?.instantiateViewController(withIdentifier: "loginForm") as! LoginViewController
let navigationVC = UINavigationController(rootViewController: vc)
navigationVC.modalPresentationStyle = .fullScreen
present(navigationVC, animated: true, completion: nil)
}
// Tap on button
#IBAction func signOutUserButton(_: UIButton) {
let alertController = UIAlertController(title: nil, message: "Are you sure you want to sign out?", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Sign Out", style: .destructive, handler: { _ in
// Condition where I check possibility to sign out
self.signOut()
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alertController, animated: true)
}
private func signOut() {
let firebaseAuth = Auth.auth()
do {
try firebaseAuth.signOut()
// If everything is okay then perform segue
showLoginViewController()
} catch let signOutError as NSError {
// Otherwise show error
print("Error signing out: %#", signOutError)
}
}
Reconnect the segue from the controller to the destination.
Connect the button to an IBAction.
In the IBAction check conditions and call performSegue(withIdentifier:sender:) on success.

Prevent presenting the UIAlertViewController after navigating to the other view

I have one scenario when the user did not use the application for more than 5 min app will show a popup with session expiration message.
The code for session expiration is added in the appDelegate and from there the popup will be presented on the current view controller.
code is
#objc func applicationDidTimeout(notification: NSNotification) {
if (window?.rootViewController?.isKind(of: UITabBarController.self))! {
for view in window?.rootViewController?.view.subviews ?? [(window?.rootViewController?.view)!] {
if view.isKind(of: MBProgressHUD.self) {
return
}
}
if window?.rootViewController?.presentedViewController != nil {
window?.rootViewController?.dismiss(animated: true, completion: {
self.showMessage(message: Message.sessionTimeout)
})
} else {
self.showMessage(message: Message.sessionTimeout)
}
}
}
fileprivate func showMessage(message: String) {
let alert = UIAlertController(title: appName, message: message, preferredStyle: .alert)
let actionOkay = UIAlertAction(title: "OK", style: .default) { (action) in
DispatchQueue.main.async {
UIView.transition(with: self.window!, duration: 0.3, options: UIView.AnimationOptions.transitionCrossDissolve, animations: {
CommonFunctions.setLoginAsRootVC()
}, completion: nil)
}
}
alert.addAction(actionOkay)
self.window?.rootViewController?.present(alert, animated: true, completion: nil)
}
Now if the user is doing some data entry and at that time, if the user leaves application ideal for 5 min or more the keyboard will dismiss and the session expiration message shown there.
But as the text field's delegate method textFieldShouldEndEditing has some validation and if that validation fails it shows a popup with the message and ok button.
So when the user taps on the ok button in the session expiration message popup, it will redirect the user to the login screen but due to the text field's delegate method validation, it shows one pop up in the login screen.
Code for the validation fail message popup is
fileprivate func showErrorMessage(message: String) {
let alert = UIAlertController(title: appName, message: message, preferredStyle: .alert)
let actionOkay = UIAlertAction(title: "OK", style: .default) { (action) in
self.txtField.becomeFirstResponder()
}
alert.addAction(actionOkay)
self.present(alert, animated: true, completion: nil)
}
How to prevent the popup from being present in the login screen?
I try to get the proper way to prevent the popup from appearing on the login screen.
But Finally, I found one heck to solve this issue.
I have declared one boolean in AppDelegate and set it's value to false when I want to prevent the popup from appearing and then revert it back to true when I want to show the popup.
I know this is not the elegant or efficient solution for the issue, but it works for now.
If anyone knows the better answer can post here, I'm still open to any better solution.
#objc func applicationDidTimeout(notification: NSNotification)
{
let visibleView : UIViewController = self.getVisibleViewControllerFrom(self.window?.rootViewController)!
self.showMessage(message: Message.sessionTimeout,Controller: visibleView)
}
fileprivate func showMessage(message: String , Controller : UIViewController) {
let alert = UIAlertController(title: appName, message: message, preferredStyle: .alert)
let actionOkay = UIAlertAction(title: "OK", style: .default) { (action) in
//Now apply your code here to set login view controller as rootview
// This controller is for demo
window!.rootViewController = UIStoryboard(name: "Main", bundle:
nil).instantiateViewController(withIdentifier: "loginview")
window!.makeKeyAndVisible()
}
alert.addAction(actionOkay)
Controller.present(alert, animated: true, completion: nil)
}
//MARK:- Supporting method to get visible viewcontroller from window
func getVisibleViewControllerFrom(_ vc: UIViewController?) -> UIViewController? {
if let nc = vc as? UINavigationController {
return self.getVisibleViewControllerFrom(nc.visibleViewController)
} else if let tc = vc as? UITabBarController {
return self.getVisibleViewControllerFrom(tc.selectedViewController)
} else {
if let pvc = vc?.presentedViewController {
return self.getVisibleViewControllerFrom(pvc)
} else {
return vc
}
}
}
Try this code, I've use this code many times may be it's work for you.

How to prevent viewing a view when clicking on a button (Swift)

I'm having a button on view that contains this condition:
FIRAuth.auth()?.addStateDidChangeListener { auth, user in
if let user = user {
self.performSegue(withIdentifier: "AddDevice", sender: nil)
}
else {
let alert = UIAlertController(title: "Sorry", message:"You Have to register", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default) { _ in })
self.present(alert, animated: true){}
}
}
To check whether the user is logged in or not!
If logged in the segue should run and open the next view
If not an alert should appear to the user.
But the next view contains a function that retrieved the current user data! (Logged in user) in viewDidLoad function!
So when the user not logged in and I click to this button I keep getting crash instead of the alert!
How can I prevent viewing the next view and just present the alert!
So I can avoid the crash?
From your description is sounds like the segue is always executing, but it's not what you want.
Connect the segue from the view controller itself, name it and put the whole method inside IBAction connected to the button.
Something like this:
#IBAction func loginDidTouch(sender: AnyObject) {
FIRAuth.auth()?.addStateDidChangeListener { auth, user in
if let user = user {
self.performSegue(withIdentifier: "AddDevice", sender: nil)
}
else {
let alert = UIAlertController(title: "Sorry", message:"You Have to register", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default) { _ in })
self.present(alert, animated: true){}
}
}
For illustration:
In the first image the segue is connected to the button. Therefore, every time the button is pressed it will execute the segue.
In the second image the segue is connected to the view controller and the button is only connected to the via the IBAction. Therefore, the button only triggers the action and the action triggers the segue.
This could easily checked by selecting the segue in storyboard

Don't show Alert when the ViewController is not in Window hierarchy

I am having a NavigationController. In the ThirdViewController I am performing some task and on failure, I show Alert messages using UIAlertController.
Sometimes, when I start the task and come back to SecondViewController, I get the error message displayed on SecondViewController and on clicking OK, everything gets black below Navigation bar. I am left with only Navigation bar and if I go back again to FirstViewController, it also has the same black view except Navigation bar.
Presenting Alert of the ViewController which is not in the window hierarchy creates the issue. I do not want the Alert to be presented if I am not on the screen.
It is easily reproducible if I go back swiping the ViewController slowly.
What is the best way to handle it?
Sharing my code,
Button action in ThirdViewController
func buttonTapped() {
APIManager.sharedManager.getDetails(completion: { (details ,error) -> Void in
guard error == nil else {
Alert.errorMsg(error!.localizedDescription, viewController: self, goBack: false)
return
}
print(details)
}
}
class Alert: NSObject {
/* Error message */
class func errorMsg(message: String, viewController: UIViewController?, goBack: Bool = false) {
let alertView = UIAlertController(title: "Error", message: message, preferredStyle: .Alert)
let action = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) { (alert: UIAlertAction) -> Void in
if goBack == true && viewController != nil {
viewController!.navigationController?.popToRootViewControllerAnimated(true)
}
}
alertView.addAction(action)
let controller = viewController ?? UIApplication.sharedApplication().keyWindow?.rootViewController
controller!.presentViewController(alertView, animated: true, completion: nil)
}
}
I created a CustomViewController and added a property 'isUnloading'. In viewWillDisappear, I set isUnloading = true. I check the property before presenting the Alert.
Since you did not share any code we don't know exactly what happens there. But if you do not want to show the alert if the view controller is not in the window hierarchy you could check if viewController.view.window is set before showing the alert view and show it only if it is set.
you can do something like,
class AlertHelper {
func showAlert(fromController controller: UIViewController) {
var alert = UIAlertController(title: "abc", message: "def", preferredStyle: .Alert)
controller.presentViewController(alert, animated: true, completion: nil)
}
}
called alert as,
var alert = AlertHelper()
alert.showAlert(fromController: self)
refer this link for more detail.
Hope this will help :)

Present a ViewController although UIAlertController is in the view Hierarchy

I want to presentViewController when an alertController is presented.
I want such as if user presses the Ok button now i want to show the ViewController from AppDelegate on PushNotification.
I came to know on SO that UIAlertController is presented on different thread so want to present the ViewController after user presses Ok Button and moves on to main thread as below:
func presentRScreenFromRootViewController()
{
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let rViewController = storyboard.instantiateViewControllerWithIdentifier("rsh") as! R_SVC
dispatch_async(dispatch_get_main_queue(),{
self.window?.rootViewController?.presentViewController(rViewController, animated: true, completion: nil)
});
}
But I get warning as:
Attempt to present <BB.R_SVC: 0x15b983600> on
<SWRevealViewController: 0x15652df00> which is already presenting
<UIAlertController: 0x15b4f2dd0>
This is because the viewcontroller is presenting alertcontroller.Other times its working well..My rootViewController changes and moves to next screen..But when there is UIAlertController i get warning as attempt to present viewcontroller which is not in the view hierarchy
What am i doing wrong?
when creating UIAlertController, place you VCpresenting code in the an UIAlertAction like so
let alert = UIAlertController(title: "your title", message "move to next VC?", preferredStyle: UIAlertController: UIAlertControllerStyle.Alert)
let action: UIAlertAction = UIAlertAction(title: "yes move to next VC", style: UIAlertActionStyle.Default) {action -> Void in
let rViewController = storyboard.instantiateViewControllerWithIdentifier("rsh") as! R_SVC
self.presentViewController(rViewController, animated: true, completion: nil) })
alert.addAction(defaultAction)
self.presentViewController(alert, animated: true, completion: nil)
edit: Maybe try Dismiss all UIAlertControllers currently presented.

Resources