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.
Related
I have a screen with several switches for the user to select an item. I want to restrict the user to only selecting one item.
I declare this variable at the top of the class, before the viewDidLoad function:
var instrumentCounter:Int = 0
I declare outlet variables for each switch:
#IBOutlet weak var guitarPlay: UISwitch!
I have a function for each switch as well (note - I have some print statements in for troubleshooting):
#IBAction func guitarSwitch(_ sender: Any) {
if self.guitarPlay.isOn == false
{
print("Top: \(instrumentCounter)")
instrumentCounter -= 1
print("Top: \(instrumentCounter)")
}
else if self.guitarPlay.isOn && instrumentCounter == 1
{
self.guitarPlay.setOn(false, animated: true)
showTooManyAlert()
print("Middle: \(instrumentCounter)")
}
else{
instrumentCounter += 1
print(instrumentCounter)
}
}
Here is the function for the alert:
func showTooManyAlert(){
let alertController = UIAlertController(title: "Alert", message:
"You can only select one instrument. Unselect another instrument to select this one.", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Okay", style: .default))
self.present(alertController, animated: true, completion: nil)
}
And here's what's happening: The function for the switch works up to a certain point. I forgot to say that when the screen loads, if the user has already selected an instrument in their profile, the instrumentCounter variable increments by 1. So even before the user does anything, that variable is set to 1.
When I tap the guitar switch (which is currently off -- another switch is on), the first part of the code is skipped and the middle part executes -- the guitar switch turns off, and the alert displays. But then the first part of the code executes: instrumentCounter -= 1.
I'm at a loss as to why that is happening.
I figured it out. I changed the switches' Sent Event from "Value Changed" to "Touch Up Inside."
I'm trying to make a password protected view controller.
so far -
Created storyboard -
on viewcontroller - created hard coded log in -
prints to console if successful or not.
textfields etc...
#IBOutlet weak var untext: UITextField!
#IBOutlet weak var pwtext: UITextField!
let username = "admin"
let password = "adminpw"
override func viewDidLoad() {
super.viewDidLoad()
pwtext.isSecureTextEntry = true
}
#IBAction func loginbtn(_ sender: Any) {
if untext.text == username && pwtext.text == password
{
print("log in succesful")
} else {
print("log in failed")
}
}
The issue I have, once I press the login button, it takes me to the admin page if successful or not.
How can I print a notification - on screen - if unsuccessful and remain on the current view controller, and if successful, take me to admin view controller?
You can either use a segue or instantiateViewController. But in this example I'll use instantiateViewController (Images). (But commented how to use a segue)
Add a class and an identifier to your secondary ViewController
Choose between my Segue or Instantiate. (Check my comments in the code)
If login is succeeded, either perform the segue or navigate using instantiate.
Happy coding. :D
But first off, let's take a look at the code you provided.
#IBAction func loginbtn(_ sender: Any)
{
if untext.text == username && pwtext.text == password
{
print("login succeeded")
//1. using instantiateViewController
if let storyboard = storyboard
{
//Check my image below how to set Identifier etc.
// withIdentifier = Storyboard ID & "ViewController" = Class
let vc = storyboard.instantiateViewController(withIdentifier: "ViewController") as! ViewController
self.present(vc, animated: false, completion: nil)
}
//2. Use segue (I'll wrap this with a comment incase you copy)
//self.performSegue(withIdentifier: "SegueID", sender: self)
}
else
{
//Setting up an "AlertController"
let alert = UIAlertController(title: "Login failed", message: "Wrong username / password", preferredStyle: UIAlertController.Style.alert)
//Adding a button to close the alert with title "Try again"
alert.addAction(UIAlertAction(title: "Try again", style: UIAlertAction.Style.default, handler: nil))
//Presentating the Alert
self.present(alert, animated: true, completion: nil)
}
}
Click on the yellow dot on your ViewController (On the ViewController where you want the login-page to take you)
Click on the icon like I've. (Which is blue) and set a Class + Storyboard ID.
NOTE! IF you wanna use a segue, make SURE you have a connection between ViewController(Login) and ViewController1
Assuming you use segues for navigation, you can put a "general purpose" segue (drag from your controller, instead of any controls in it) and assign it an ID (Identifier in attribute inspector of the segue in Storyboard). After that you can conditionally invoke segue from the parent controller class with your code:
if passwordCorrect {
performSegue(withIdentifier: "SegueID", sender: nil)
}
Guys i am facing an odd problem with NavigationController. Existing answers did not help at all !!!!
Here is basic scenario of the app:
There are two views - Main and Second view
In main view there is a button when i happen to tap goes into second view using segue.
In second view after i enter a certain field in text view and click on a button called "join" it triggers "joinMeeting()" function
and meeting should be joined.
However, when i do that debugger shows me:
"Warning: Attempt to present on
<***.ViewController: *****> whose view is not in the window
hierarchy!"
So i have read most of the tread and given that it happens because of viewDidAppear method but i have nth to be done before viewDidAppear. Everything happens after button is clicked.
joinMeeting() is successfully called and print method returns 0 which means no issue(https://developer.zoom.us/docs/ios/error-codes/) and successful SDK connection however after this "Warning" error is shown in debugger and nothing happens in the app.
If it helps following is the code that triggers joinBtn:
/**
Triggers when Join Button is clicked from second view.
*/
#IBAction func joinMeeting(_ sender: Any) {
if( activityID.text == "" ) {
let alert = UIAlertController(title: "Field is Blank", message: "Activity ID cannot be blank.", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
return;
}
let ms: MobileRTCMeetingService? = MobileRTC.shared().getMeetingService()
if ms != nil {
ms?.delegate = self;
// //For Join a meeting
let paramDict: [AnyHashable: Any] = [
kMeetingParam_Username: kSDKUserName,
kMeetingParam_MeetingNumber: activityID.text!,
]
let ret: MobileRTCMeetError? = ms?.joinMeeting(with: paramDict)
print("onJoinaMeeting ret:\(String(describing: ret))")
}
}
Please help if anyone knows or have an idea on what i am missing here.
Here is what solved the issue:
Storyboard Configuration:
ViewController --Segue: Show--> JoinViewController
#IBAction func onClickJoin(_ sender: AnyObject) {
//Main storyBoard
let initialVC = UIStoryboard(name: "Main", bundle:nil).instantiateInitialViewController() as! UIViewController
let appDelegate = (UIApplication.shared.delegate as! AppDelegate)
appDelegate.window?.rootViewController = initialVC
//Rest of the code
}
Just Add Following code on that controller in which you want to perform calling:
override func viewWillAppear(_ animated: Bool) {
let appDelegate = UIApplication.shared.delegate as? AppDelegate
appDelegate?.window?.rootViewController = self
}
Unfortunately, none of the above solutions worked for me.
So Here is my solution.
Add this line
MobileRTC.shared().setMobileRTCRootController( self.navigationController)
=> When user taps of Join Call Button.
Make sure these conditions should meet as well.
ViewController which is used to open the ZOOM meeting should be a part of root navigation controller
DO NOT present modally the current Zoom Meeting Joining View Controller. Always push it to the root navigation controller.
I have a login view controller where it should prevent the user from transition to the next view controller via a segue called "toMasterTab". I think the logic might be wrong - if the user entered the correct credentials and is not empty, it transitions fine, but if the user entered no credentials (nil) and entered the wrong credentials, then it should prevent the segue. So far, I can only get the UIAlertView to pop up, but other than that, I can't solve this...
#IBAction func loginButton(sender: AnyObject) {
let RedPlanetUser = RPUsername.text
let RedPlanetUserPassword = RPUserPassword.text
PFUser.logInWithUsernameInBackground(RedPlanetUser!, password: RedPlanetUserPassword!) {
(user: PFUser?, error: NSError?) -> Void in
if user != nil {
// Do stuff after successful login
self.performSegueWithIdentifier("toMasterTab", sender: self)
print("User logged in successfully")
} else {
// Login failed
print("User log in failed")
// Present alert
var alert = UIAlertView(title: "Login Failed",
message: "The username and password do not match.",
delegate: self,
cancelButtonTitle: "Try Again")
alert.show()
func shouldPerformSegueWithIdentifier(identifier: String!, object: AnyObject) -> Bool {
let identifier = "toMasterTab"
// prevent segue
return false
}
}
}
}
I believe you should be overriding the
override func shouldPerformSegueWithIdentifier
The problem was that the segue was connected to the button, so it automatically performed the segue even when the conditions were NOT met. I connected the segue from VC1 to VC2 and used the following code when the conditions were met, and didn't call the segue when the conditions were erroneous:
self.performSegueWithIdentifier("toMasterTab", sender: self)
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.