Conditional loading of ViewControllers: Temporary display of wrong ViewController - ios

I am trying to write a login process for my app. I have embedded a navigation controller to HomeViewController and set it as the initial ViewController. How can I fix it such that when a user enters the wrong credentials the HomeViewController will not be shown at all?
This is what it is doing:
Correct credentials entered
Display LoginViewController -> User inputs credentials -> Display HomeViewController
Wrong credentials entered
Display LoginViewController -> User inputs credentials -> Display HomeViewController -> Display LoginViewController
Code for LoginViewController (look at the last block of code)
func handlingAuthentication(notification: NSNotification) {
let dict = notification.object as! NSDictionary
if dict["error"]! as! Bool == true {
let errorMessage = dict["message"] as! String
//initialize Alert Controller
let alertController = UIAlertController(title: "Authentication error", message: errorMessage, preferredStyle: .Alert)
//Initialize Actions
let okAction = UIAlertAction(title: "Ok", style: .Default){
(action) -> Void in
self.dismissViewControllerAnimated(true, completion: nil)
}
//Add Actions
alertController.addAction(okAction)
//Present Alert Controller
self.presentViewController(alertController, animated: true, completion: nil)
}
else
{
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "isUserLoggedIn")
NSUserDefaults.standardUserDefaults().synchronize()
self.dismissViewControllerAnimated(true, completion:nil)
}
}
Code for HomeViewController
override func viewDidAppear(animated: Bool) {
let isUserLoggedIn = NSUserDefaults.standardUserDefaults().boolForKey("isUserLoggedIn")
if(!isUserLoggedIn){
self.performSegueWithIdentifier("toLoginVC", sender: self)
}
}
UPDATE
I've tried placing the code block in ViewDidLoad but I am still getting the same issue (in fact now I'm stuck on the homePage)
override func viewDidLoad() {
super.viewDidLoad()
let isUserLoggedIn = NSUserDefaults.standardUserDefaults().boolForKey("isUserLoggedIn")
if(!isUserLoggedIn){
self.performSegueWithIdentifier("toLoginVC", sender: self)
}
usernameLabel.text = Data.sharedInstance.userName
getTaskDetails()
displayTask.dataSource = self
}

If the main view controlled decides and displays the login you will inevitable see it on screen because it's already in the process of displaying - so it shouldn't do it. You should have some other controller, perhaps a splash view controller, which decides to show either the login or the main view.
In your login view controller the alert OK button calls dismiss, this is the reason the login controller disappears and re-appears again (after showing the main controller for a short time).

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.

How to present an alert after view controller has been dismissed

I have an app where a user uploads a file in the background that usually takes a couple of seconds. The upload is kicked off when they tap a "Done" button and that also dismisses the view controller. What I would I would like to happen is an alert comes up when the download is done. I thought I would just add the code below to the upload function but it isn't working. How can I have an alert box appear to confirm that that the upload was successful?
#IBAction func tapDone(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
if let image = newImage {
submit2Parse(image: image)
}
}
func submit2Parse (image: UIImage) {
if let pfImage = image2PFFile(image: image) {
// Insert PFFile into parse server
let submittedImage = PFObject(className: "Images")
submittedImage["imageFile"] = pfImage
submittedImage["type"] = "submittedFromUserHome"
submittedImage["bride"] = brideSwitch.isOn
submittedImage["groom"] = groomSwitch.isOn
submittedImage["user"] = userSwitch.isOn
submittedImage["picturePeriod"] = pickerSelected
submittedImage["uploadedByUserId"] = PFUser.current()?.objectId ?? "" submittedImage["uploadedByUserName"] = PFUser.current()?.username ?? ""
if !txtIsPlaceHolder { submittedImage["description"] = imageDescription.text }
submittedImage.saveInBackground { (success, error) in
if success {
let message = "Save in bg worked"
print(message)
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertAction.Style.default, handler: {
(action) in
self.dismiss(animated: true, completion: nil)
}))
self.present(alert,animated: true, completion: nil)
} else {
print(error?.localizedDescription ?? "")
}
}
}
}
The code gives me this build error:
Implicit use of 'self' in closure; use 'self.' to make capture semantics explicit
In your tapDone method, you need to utilize the completion of the dismissal of your controller, like so:
self.dismiss(animated: true) {
if let image = newImage {
self.submit2Parse(image: image)
}
}
This only means that the self.submit2Parse(image: image) will ONLY be executed after you dismiss the controller. Additionally, the error
Implicit use of 'self' in closure; use 'self.' to make capture
semantics explicit
only means that you need to use self. to explicitly call a variable or method when you're inside a closure. So your submit2Parse... would now be like this:
self.submit2Parse(image: image).
If I understood your question correctly you want to dismiss your SecondViewController when user tap on tapDone button. And after that once your image upload complete then you want to present alert for success.
But if you dismiss it once button tapped you won't get any alert because your SecondViewController is no longer in window hierarchy and if you will try to present the alert you will get an error in Console like:
Attempt to present on
whose view is not in the
window hierarchy!
To solve this problem you need to present a alert from your first view controller which will appear after you dismiss your second view controller and you can achieve it with delegate.
Consider the below example:
First of all declare a protocol outside your FirstViewController:
protocol UplaodSuccessDelegate:class {
func uploadSuccess(message: String)
}
then confirm your delegate to same class:
class ViewController: FirstViewController, UplaodSuccessDelegate {
then you need to pass the delegate when you present SecondViewController:
let vc = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
vc.delegate = self. //Pass delegate
self.present(vc, animated: true, completion: nil)
and add delegate method to same class:
func uploadSuccess(message: String) {
let message = "Save in bg worked"
print(message)
let alert = UIAlertController(title: "title", message: message, preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertAction.Style.default, handler: {
(action) in
}))
self.present(alert,animated: true, completion: nil)
}
now in your SecondViewController you need to add
weak var delegate: UplaodSuccessDelegate?
and in your tapDone method replace a code with:
self.dismiss(animated: true) {
if let image = newImage {
submit2Parse(image: image)
}
}
and once your upload complete you need to call delegate method (once your upload completes) like:
self.delegate?.uploadSuccess(message: "your message")
This will call your delegate method from FirstViewController.
When you drop the function below into the viewController that initiates the data upload in the background, and then call this function in the closure that fires when the submit completes it is able to successfully create an alert even thought the initial view controller was dismissed (or long dismissed if it was a long upload).
// This is the function that performs my background upload
func submit2Parse (image: UIImage) {
if let pfImage = image2PFFile(image: image) {
// Insert PFFile into parse server
let submittedImage = PFObject(className: "Images")
submittedImage["imageFile"] = pfImage
submittedImage["imageCreateDt"] = newImageCreateDate
submittedImage["type"] = "submittedFromUserHome"
submittedImage["bride"] = brideSwitch.isOn
submittedImage["groom"] = groomSwitch.isOn
submittedImage["user"] = userSwitch.isOn
submittedImage["picturePeriod"] = pickerSelected
submittedImage["uploadedByUserId"] = PFUser.current()?.objectId ?? ""
submittedImage["uploadedByUserName"] = PFUser.current()?.username ?? ""
if !txtIsPlaceHolder { submittedImage["description"] = imageDescription.text }
// Get the image timestamp, every photo has one
// How do you get the thumbnail image too?
submittedImage.saveInBackground { (success, error) in
if success {
let message = "Save in bg worked"
print(message)
self.showAlertFromAppDelegates()
} else {
print(error?.localizedDescription ?? "")
}
}
}
}
// This is the function that creates the alert
func showAlertFromAppDelegates(){
var topWindow: UIWindow? = UIWindow(frame: UIScreen.main.bounds)
topWindow?.rootViewController = UIViewController()
topWindow?.windowLevel = UIWindow.Level.alert + 1
let alert: UIAlertController = UIAlertController(title: "Upload Complete", message: "Your image was successfully submitted!", preferredStyle: .alert)
alert.addAction(UIAlertAction.init(title: "OK", style: .default, handler: { (alertAction) in
topWindow?.isHidden = true
topWindow = nil
}))
topWindow?.makeKeyAndVisible()
topWindow?.rootViewController?.present(alert, animated: true, completion:nil)
}

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.

Send an Integer back to the previous View Controller

Hello I am trying to pass back a variable that has the level number. After the user clears the round below is called
if enemyHP <= 0.0{
level = level + 1
battle(winPlayer:true)
}
then function battle is called
let alert = UIAlertController(title: "finished!", message: finishedMessage, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title:"Go Back",style:.default,handler:{action in
self.dismiss(animated: true, completion: nil)}))
self.present(alert,animated: true,completion: nil)
}
I am trying to display the level number in the previous view controller so I need the variable level to go be passed back. What is the best way for this? I have used segues and it doesn't work with the alert.
Delegate
protocol DataSentDelegate {
func Back(data: Int)
}
I used
delegate?.Back(data: level)
In the previous ViewController I added
func Back(data: Int){
new = data
}
print(new)
It doesn't appear.
Instead of using the dismiss-present methods, you should use a segue and then pass the object in prepare(for:sender:) this way:
func prepare(for segue:UIStoryboardSegue, sender:Any?)
{
if segue.identifier == "Back" // Give it any name
{
if let destination = segue.destination as? MyViewController // NOTE: this is your custom view controller class
{
destination.level = yourDesiredValue
}
}
}
Update: to perform the segue:
let alert = UIAlertController(title: "finished!", message: finishedMessage, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title:"Go Back",style:.default,handler:{action in
self.performSegue(withIdentifier: "Back", sender: nil)
}
self.present(alert,animated: true,completion: nil)

Signup not proceeding to Tab View Controller

Once a user signs in to my app, I need to take them to a tab view controller so they can use the app to its fullest potential. I have tried to initiate the TabBarController in the buttons onClick function with no success.
PFUser.logInWithUsernameInBackground(username, password:password) {
(user: PFUser?, error: NSError?) -> Void in
if error == nil {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("KlikurHomeTabs") as! UITabBarController
self.presentViewController(vc, animated: true, completion: nil) // this shows it modally
} else if error!.code == 101 {
var invalidLogin:UIAlertView = UIAlertView(title: "Please try again", message: "The username password combo you have us does not match our records, please try again or reset your password.", delegate: self, cancelButtonTitle: "Try again")
invalidLogin.show()
}
}
Can anybody spot what I am doing wrong? I have no clue and have been trying for a while now. Thanks :)
Just change this line and also take out the storyboard line of code
let vc = storyboard.instantiateViewControllerWithIdentifier("KlikurHomeTabs") as! UITabBarController
To
let vc = self.storyboard.instantiateViewControllerWithIdentifier("KlikurHomeTabs")
self.presentViewController(vc!, animated: true, completion: nil)
This is more of an elaboration on Paulw11's comment, but here is what I am doing. I have a "splash" view controller that has 2 storyboard segues, one to a signin/signup view controller, and one to my main tabview controller. Inside viewDidAppear, I check the login status and then perform one of the two segues. Here is an example of the code.
override func viewDidAppear(animated: Bool) {
if needsLogin {
performSegueWithIdentifier("SignIn/SignUP", sender: self)
} else {
performSegueWithIdentifier("MainTabBar", sender: self)
}
}

Resources