Protected View embedded in Tab View Controller? - ios

I'm working on an app that should be hidden behind login view.
So in general, I have a tab navigation controller which should hold pretty much the entire app and my logic is when the app loads, the initial View controller is the tab navigation controller, which shows its first View IF the user is logged in. If it's not logged in, they should see login/register page. Both login and register page work with Parse, they are ok and function well. They are presented Modally (using segues) on top of the first View in the Tab view controller.
The issue is that when I login (and I confirm that it works!) the login View controller is not dismissed in order to see the tab view controller and I think that I might have messed something up in the segues. The segue to present the login view if the user is not logged in is made from the protected view (not it's navigation controller, although i tested that too, doesn't work) to login view controller.
Also, the code in the protected page is like this:
override func viewDidAppear(animated: Bool) {
self.performSegueWithIdentifier("segueToLoginView", sender: self)
}
Here's what my story board looks like:
So, the login segue is presented modally and here's my code for the login button.
#IBAction func loginButtonTapped(sender: AnyObject) {
let username = usernameTextField.text
let password = passwordTextField.text
// Sends to Parse to see if user exists
PFUser.logInWithUsernameInBackground(username!, password: password!) {
(user: PFUser?, error: NSError?) -> Void in
if user != nil {
// LOGIN Successful
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "isUserLoggedIn")
NSUserDefaults.standardUserDefaults().synchronize()
self.dismissViewControllerAnimated(true, completion: nil)
print("User logged in")
} else {
// Display Failed Login Alert message with confirmation
let failedLoginAttepmt = UIAlertController(
title: "Ups!",
message: "Something went wrong, try again...",
preferredStyle: UIAlertControllerStyle.Alert
)
let confirmAction = UIAlertAction(
title: "OK!",
style: UIAlertActionStyle.Default)
{ action in
self.dismissViewControllerAnimated(true, completion: nil)
}
failedLoginAttepmt.addAction(confirmAction)
self.presentViewController(failedLoginAttepmt, animated: true, completion: nil)
print("Could not find the user")
}
and this line of code self.dismissViewControllerAnimated(true, completion: nil) should dismiss the modally presented login view controller, as I do see the print statement in the console.
Where's my mistake?

Ugh, I figured it out.
I forgot to add logic for checking if the user is actually logged in! If you don't have logic, the View controller will simply show you the login view controller over and over again.

Related

Disable "Save Password" action sheet when exiting UIViewController?

My app has a "Create Account" view controller (shown below) that prompts the user to enter a username and password. Whenever I segue to another view controller, I get a pop-up action sheet prompting to save the password in the keychain.
This is a nifty little freebie IF the user successfully creates the new account. But I get this same pop-up if the user hits the cancel (back) button in the navigation bar, if they select the option to use Facebook login instead of creating an account, or any other means for leaving this view controller (see figures below).
How can I get this popup to ONLY show up when the user successfully creates a new account?
EDIT: Per request, here is the code that is related to the segues that result in the appearance of the "Save Password" action sheet.
from CreateAccountViewController.swift:
class CreateAccountViewController : UIViewController
{
// ... bunch of irrelevant code deleted ...
// bound to "Connect with Facebook" button (see image below)
#IBAction func switchToFacebook(_ sender : UIButton)
{
performSegue(.SwitchToFacebookLogin, sender: sender)
}
// ... bunch of irrelevant code deleted ...
}
extension CreateAccountViewController : GameServerAlertObserver
{
// callback based on response from GameCenter after
// submitting a "create new user" request
func handleConnectionResponse(_ response:GameServerResponse )
{
switch response
{
// ... other response cases removed ...
case .UserCreated:
self.removeSpinner()
performSegue(.CreateAccountToStartup, sender: self)
default:
response.displayAlert(over: self, observer: self)
self.removeSpinner()
}
}
// Functions defined in the GameServerAlertObserver protocol
// to handle user response to "User Exists Popup" (figure below)
func ok()
{
// user chose to enter new password... clear the existing username field
usernameTextField.text = ""
}
func cancel()
{
// segue back to the startup view controller
performSegue(.CreateAccountToStartup, sender: self)
}
func goToLogin()
{
// segue to the login view controller
performSegue(.SwitchToAccountLogin, sender:self)
}
}
from UIViewController_Segues:
enum SegueIdentifier : String
{
case LoserBoard = "loserBoard"
case CreateAccount = "createAccount"
case AccountLogin = "accountLogin"
case FacebookLogin = "facebookLogin"
case SwitchToFacebookLogin = "switchToFacebookLogin"
case SwitchToAccountLogin = "switchToAccountLogin"
case CreateAccountToStartup = "createAccountToStartup"
case AccountLoginToStartup = "accountLoginToStartup"
case FacebookLoginToStartup = "facebookLoginToStartup"
case UnwindToStartup = "unwindToStartup"
}
extension UIViewController
{
func performSegue(_ target:SegueIdentifier, sender:Any?)
{
performSegue(withIdentifier: target.rawValue, sender: sender)
}
}
from GameServerAlert.swift:
protocol GameServerAlertObserver
{
func ok()
func cancel()
func goToLogin()
}
extension GameServerResponse
{
func displayAlert(over controller:UIViewController, observer:GameServerAlertObserver? = nil)
{
var title : String
var message : String
var actions : [UIAlertAction]
switch self
{
// ... deleted cases/default which don't lead to segue ...
case .UserAlreadyExists:
title = "User already exists"
message = "\nIf this is you, please use the login page to reconnect.\n\nIf this is not you, you will need to select a different username."
actions = [
UIAlertAction(title: "Go to Login page", style: .default, handler: { _ in observer?.goToLogin() } ),
UIAlertAction(title: "Enter new username", style: .default, handler: { _ in observer?.ok() } ),
UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in observer?.cancel() } )
]
}
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
actions.forEach { (action) in alert.addAction(action) }
controller.present(alert,animated:true)
}
}
Examples from the simulator:
Create Account - (user enters username and password for new account here.)
Facebook Login
If user decides to use Facebook to log in rather than creating a user account, they are taken to this view (which I still haven't fleshed out). Note that the "Save Password" action sheet has popped up.
User Exists Popup
If user attempts to create an account with a username that already exists, they will be presented with this popup. If they select Cancel, they are taken back to the startup screen (see below). If they select Enter new username, they are kept on the same screen with the username cleared out. If they select Login, they are taken to the login screen.
Startup Screen
If the user selects Cancel above, they are brought back here. Again, note that the "Save Password" action sheet has popped up.
What I do to avoid the automatic Password saving action sheet when the user :
dismiss the login view controller ;
pop the view controller ;
use interactive pop gesture.
=>
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
passwordTextField.textContentType = .password
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if isMovingFromParent || isBeingDismissed || parent?.isBeingDismissed == true {
passwordTextField.textContentType = nil
}
}
Sorry about the short answer, I don't usually post on this site. This is the password Autofill that is happening on your device when the create user screen is dismissed.
Apple Documentation: https://developer.apple.com/documentation/security/password_autofill
Here is a link to a site that goes over all the requirements very well: https://developerinsider.co/ios12-password-autofill-automatic-strong-password-and-security-code-autofill/
Add a condition before running the code block which shows the action sheet. You can do this simply with an if statement. This statement must check if the account has been successfully created or not. Code block which shows action sheet must run only when the condition is true.

Swift 4 Attempt to present ViewController whose view is not in the window hierarchy

I am using swift 4 and I am trying to create an alertView when I there is an error while signing up a user using Firebase. I have an IBAction for the sign up button which will sign the user up using text from two textfields, one for email and one for password.
I am basically trying to show an alertview when there is an error with the sign up process, for example there is an empty textfield.
I have attached a screenshot of the function to where that is occuring. I know that I am in fact getting an error because the print statement outputs an error if there is one.
Regardless of if there is an error or not, there is no alert view showing up and the app performs the segue regardless.
2019-01-15 21:40:26.368924-0500 Pronto[9036:225268] Warning: Attempt
to present on
whose view is not in the
window hierarchy
This is the output that I am getting for the alertview now showing up.
I have looked at all the other posts about this same issue but none seem to work.
This issue happens due to your view hierarchy.
You need to find out what is your Current/Topmost view controller in
view hierarchy and present your alert over it.
To find out topmost view controller use following code:
func getTopMostViewController() -> UIViewController? {
var topMostViewController = UIApplication.shared.keyWindow?.rootViewController
while let presentedViewController = topMostViewController?.presentedViewController {
topMostViewController = presentedViewController
}
return topMostViewController
}
And present your alert over topmost view controller and use main thread to present an alert because closures may have working on another thread.
DispatchQueue.main.async {
getTopMostViewController()?.present(alertController, animated: true, completion: nil)
}
Please refer to this stack answer:
Swift 3 Attempt to present whose view is not in the window hierarchy
Try using ViewDidAppear instead of View did Load.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let alertViewController = UIAlertController(title: "Any", message: "Any Custom Message", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "ANy", style: .cancel, handler: nil))
present(alertViewController, animated: true, completion: nil)
}
You can get the top most view controller and have that view controller present the alert. So instead of self.present use this approach and see if it works:
let topViewController = UIApplication.shared.keyWindow?.rootViewController
topViewController?.present(alertController, animated: true, completion: nil)
Also try presenting on the main thread, since you're trying to show the alert in createUser completion handler:
DispatchQueue.main.async {
self.present(alertController, animated: true, completion: nil)
}
Check if you have "double tap" issue:
You double tap button accidentally
signUpBtnPressed is called twice
First request is executed correctly, thus launching the segue, and the error is nil
Second request returns error like 'user already exists', then trying to show alert from current controller, but segue is already launched and next controller is already presented
This is fixed by using loader with UI blocking (for example SVProgressHUD) - start loader at the beginning of the method and dismiss it in callback.

Can't dismiss UIViewController after Successful Login

I'm struggling to dismiss a UIViewController from the NavigationStack after login is completed.
The login screen is a UIViewController is presented with this line of code
let loginController = LoginController()
self.present(loginController, animated: true, completion: nil)
And then I run this code to log the user in via firebase.
Auth.auth().signIn(withEmail: email, password: password) { (user, err) in
if let err = err {
print("Failed to sign in user with email", err )
}
//self.dismiss(animated: true, completion: nil)
let userProfileVC = UserProfileController()
let navController = UINavigationController(rootViewController: userProfileVC)
self.navigationController?.pushViewController(navController, animated: true)
}
As you can see I have tried the pushViewController method, and have also tried the commented self.dismiss method? Nothing I do seems to remove the loginController UIView and take me back to the UINavigationController home screen. Can anyone help me out, thanks a lot.
you can do as this
self.dismiss(animated: true, completion: {
let userProfileVC = UserProfileController()
let navController = UINavigationController(rootViewController: userProfileVC)
self.navigationController?.pushViewController(navController, animated: true)
})
You might have to print your viewstack and show it to us in order to understand better but try one of these methods:
navigationController?.popViewController(animated: true)
or
navigationController?.popToRootViewController(animated: true)
The first method will dispose of the most recently pushed view controller, while the second will remove all but the navigation controllers "home screen" which you seek.
You can not push to particular controller while any controller is present on current controller. so you can use protocol delegate on controller which is present current controller. when delegate method called then you can push to your controller.

Swift Firebase login

I'm having a problem where every time I enter the right credentials, it brings me to one view controller then opens up the same view controller again even though I only have the login viewer controller linked to one view controller. If I don't enter the right credentials it still brings me into the linked view controller. Here is the code.
EDIT: Using a push segue(show)
#IBAction func loginTapped(_ sender: Any) {
if let Email = userEmail.text, let Pass = userPassword.text{
Auth.auth().signIn(withEmail: Email, password: Pass, completion: { (user, error) in
if error != nil{
print("incorrect")
}
else{
if error == nil{
self.performSegue(withIdentifier: "loginPage", sender: self)
print("correct")
}
}
})
}
}
I don't know if you've fixed your problem, but check your storyboard. Sounds like you have a segue connected from the button to the next ViewController which would result in pressing the button and it'll always push that ViewController.
To do this easily just see if you have a segue connected from the button to your destination ViewController in your MainStoryboard.

Presenting view controllers on detached view controllers is discouraged - screen view turns black

I have two view controllers: VC1 and VC2. From VC1, press a button will navigate app to VC2. VC2 has a network request function in viewDidLoad() which shows an alert if the request is failed.
If everything is fine, no request failure on VC2, when I move back to VC1, it would call to deinit function of VC2.
However, if the request failed and an error alert is shown, the deinit function (of VC2) wouldn't be called when I move back VC1. Moreover, it causes an error of "Presenting view controllers on detached view controllers is discouraged" while it's trying to show that alert after the screen displays VC1, screen then turns black except for navigation bar and the error alert of VC2 is shown on VC1 (The reason is when the VC2 is going to present the error alert, user suddenly press the back button on navigation bar to move back to previous screen). My alert is a global variable.
Here is the code I handle request and show alert on VC2:
func sendRegisterRequest() {
registerAPI.request(parameters: parameters) {
[weak self] (response) in
if let strongSelf = self {
strongSelf.handleResponse(response)
}
}
}
func handleResponse(response: Response<AnyObject, NSError>) {
let result = ResponseHandler.responseHandling(response)
if result.messageCode != MessageCode.Success {
// Show alert
handleResponseError(LocalizedStrings.registerFailedTitle, message: result.messageInfo, requestType: .Register)
return
}
}
func handleResponseError(title: String, message: String?, requestType: RequestType?) {
alert = UIAlertController(title: title, message: message, preferredStyle: .Alert)
let action = UIAlertAction(title: LocalizedStrings.okButton, style: UIAlertActionStyle.Default) { (action) -> Void in
self.handleAlertViewAction(requestType)
}
alert.addAction(action)
dispatch_async(dispatch_get_main_queue(), {
self.presentViewController(self.alert, animated: true, completion: nil)
})
}
I attach the screenshot here:
Could anyone have solution for this issue? Any help could be appreciated,
Lucy Nguyen.
I've got the same problem when I build my app. To solve this problem, I tried many changes and finally removed the error message.
I made an alert window in first VC to give user some notice. And I write the alert control code in - (void)viewDidLoad. I think you did it same or in - (void)viewWillAppear.
Just move your alert code to - (void)viewDidAppear. Then, error will be gone.

Resources