How to perform a segue only if a condition is true - ios

I have two view controllers, the first contains a button and when I press that button I want it to check the textField I have to see if It contains any value and if it doesn't. I want a alert to appear saying to "input a value". If it does contain a value I would like to transfer to the next view controller.
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "segue1"{
if txtField.text?.isEmpty == true{
//alert "input value"
return false;
}
else{
}
//segue occurs no problem
return true;
}
I've tried many other ways to do this and none seem to work. Any information is appreciated, Thank you. (New to swift and Stack Overflow Sorry)

Your reasoning is correct. Check the following points to make sure your code is working:
You have a segue from your button to your destination view controller.
Your segue identifier is segue1 as in your code.
Your isEmpty works when the UITextField is empty.
If those are correct you need to override shouldPerformSegue(withIdentifier:sender:) as you did but you can improve the code and add the alert by doing the following:
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "segue1" {
if txtField.text?.isEmpty {
let alertController = UIAlertController(
title: "Alert",
message: "Input value",
preferredStyle: .alert
)
present(alertController, animated: true, completion: nil)
return false
}
}
return true
}
For more information about how to use UIAlertController check the Documentation

Related

How do I stop a segue from going through programmatically in Swift?

Here is the code:
#IBAction func loginTapped(_ sender: Any) {
let email = emailTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let password = passwordTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
Auth.auth().signIn(withEmail: email, password: password) { (result, error) in
if error != nil {
self.errorLabel.text = error!.localizedDescription
self.errorLabel.alpha = 1
print(error!.localizedDescription)
}
else {
self.performSegue(withIdentifier: "loginSegue", sender: nil)
print("User is signed in with Firebase.")
}
}
}
I have a segue, loginSegue, connected from the login button to the homeViewController. Within in the if error statement I would like to stop the segue from going through because the user has not signed in. The goal here is not allow the user to go forward if they get an error. Is there an "opposite" to the performSegue(withIdentifier: String, sender: Any?) ?
First , there is no "opposite" to performSegue(withIdentifier: String, sender: Any?).
But the issue is not about this. I think you wired the segue from the login button and gave it an identifier. If you wire a segue from a button directly the button is always going to execute that segue. Doing some operations in the button's action does not effect.
You need to wire a segue from FirstVc to SecondVc (not from the button) and then give the segue an identifier. Then, from the button's action you can check if there is no error and call performSegue(withIdentifier: String, sender:) passing your segue's identifier.
I think your button is connected to perform segue in storyboard. So your button has two actions - one from storyboard to perform segue and second in your code. Just remove the connection from storyboard and connect only UIViewControllers not with your button.
You could override the shouldPerformSegue(withIdentifier:,sender:) method and return false if the login fails and you don't want to perform the segue. Here's an example
override func shouldPerformSegue(withIdentifier identifier: String?, sender: Any?) -> Bool {
if let ident = identifier {
if ident == "YourIdentifier" {
if loginSuccess != true {
return false
}
}
}
return true
}

Swift - password protected view controller

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)
}

Reloading TableView when a UIViewController is being dismissed?

The problem here is that I'm presenting EditCommentVC modally, over the current context of the CommentVC because I want to set the background of the UIView to semi-transparent. Now, on the EditCommentVC I have a UITextView that allows the user to edit their comment, along with 2 buttons - cancel (dismisses the EditCommentVC) and update that updates the new comment and push it to the database.
In term of code, everything is working, except that once the new comment is being pushed and EditCommentVC is being dismissed, the UITableView on CommentsVC with all the comments is not being reloaded to show the updated comments. Tried calling it from viewWillAppear() but it doesn't work.
How can I reload the data in the UITableView in this case?
#IBAction func updateTapped(_ sender: UIButton) {
guard let id = commentId else { return }
Api.Comment.updateComment(forCommentId: id, updatedComment: editTextView.text!, onSuccess: {
DispatchQueue.main.async {
let commentVC = CommentVC()
commentVC.tableView.reloadData()
self.dismiss(animated: true, completion: nil)
}
}, onError: { error in
SVProgressHUD.showError(withStatus: error)
})
}
The code in the CommentVC where it transitions (and passes the id of the comment). CommentVC conforms to a CommentActionProtocol that passes the id of that comment:
extension CommentVC: CommentActionProtocol {
func presentActionSheet(for commentId: String) {
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let editAction = UIAlertAction(title: "Edit", style: .default) { _ in
self.performSegue(withIdentifier: "CommentVCToEditComment", sender: commentId)
}
actionSheet.addAction(editAction)
present(actionSheet, animated: true, completion: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "CommentVCToEditComment" {
let editCommentVC = segue.destination as! EditCommentVC
let commentId = sender as! String
editCommentVC.commentId = commentId
}
}
}
I see atleast 2 problems here:
You are creating a new CommentVC which you should not do if you want to update the tableView in the existing view controller.
Since you have mentioned that Api.Comment.updateComment is a an asynchronous call, you need to write the UI code to run on the main thread.
So first you need to have the instance of the commentVC in a variable inside this viewController. You can store the instance of the view controller from where you are presenting this view controller.
class EditCommentVC {
var commentVCdelegate: CommentVC!
// Rest of your code
}
Now you need to pass the reference commentVC in this variable when you are presenting the edit view controller.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "CommentVCToEditComment" {
let editCommentVC = segue.destination as! EditCommentVC
let commentId = sender as! String
editCommentVC.commentId = commentId
editCommentVC.commentVCdelegate = self
}
}
Now you need to use this reference to reload your tableView.
Api.Comment.updateComment(forCommentId: id, updatedComment: editTextView.text!, onSuccess: {
DispatchQueue.main.async {
commentVCdelegate.tableView.reloadData() // - this commentVC must be an instance that you store of the your commentVC that you created the first time
self.dismiss(animated: true, completion: nil)
}
}, onError: { error in
SVProgressHUD.showError(withStatus: error)
})
Well, i had this problem too, and the solution i found was to use Protocol. I would recommend you to search how to send data back to previous ViewController. That way, when you dismiss the EditCommentVC, you then send back a value(in my case i send true) to the previous ViewController(in your case, CommentVC), and then you'll have a function in CommentVC checking if the value is true and if it is, reload the TableView.
Here, let me show you an example of how i used (those are the names of my ViewControllers, functions and protocols, you can use whatever you want and send whatever data you want back):
In your CommentVC, you'll have something like this:
protocol esconderBlurProtocol {
func isEsconder(value: Bool)
}
class PalestranteVC: UIViewController,esconderBlurProtocol {
func isEsconder(value: Bool) {
if(value){
//here is where you can call your api again if you want and reload the data
tableView.reloadData()
}
}
}
Also, dont forget that you have to set the delegate of EditCommentVC, so do it when you're presenting EditCommentVC, like this:
let viewController = (self.storyboard?.instantiateViewController(withIdentifier: "DetalhePalestranteVC")) as! DetalhePalestranteVC
viewController.modalPresentationStyle = .overFullScreen
viewController.delegate = self
self.present(viewController, animated: true, completion: nil)
//replace **DetalhePalestranteVC** with your **EditCommentVC**
And in your EditCommentVC you'll have something like this:
class DetalhePalestranteVC: UIViewController {
var delegate: esconderBlurProtocol?
override func viewWillDisappear(_ animated: Bool) {
delegate?.isEsconder(value: true)
}
}
That way, everything you dismiss EditCommentVC, you'll send back True and reload the tableView.

Swift property nil outside unwindToViewController

To pass data between views, I decided to use a "temporary" object that would act as the data model of my views.
var tempMedecine = TempMedecine()
var xValue = 0
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let dmController = segue.destinationViewController as? JRBDosageMainTableViewController {
dmController.tempMedecine = self.tempMedecine
}
}
#IBAction func unwindToViewController(segue: UIStoryboardSegue) {
if let dosageController = segue.sourceViewController as? JRBDosageMainTableViewController {
self.tempMedecine = dosageController.tempMedecine!
self.xValue = 10
let dosageCell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 1, inSection: 0))
dosageCell?.detailTextLabel?.text = String(self.tempMedecine.dosageQuantity!) + " " + self.tempMedecine.dosageQuantityType!
}
}
override func shouldPerformSegueWithIdentifier(identifier: String, sender: AnyObject?) -> Bool {
if identifier == "saveMedecine" {
print(xValue)
guard tempMedecine.name != nil else {
Common.genericAlertController(self, title: "Error", message: "You need to define a name", preferedStyle: .Alert)
return false
}
guard self.tempMedecine.dosageQuantityType != nil else {
Common.genericAlertController(self, title: "Error", message: "You need to set a quantity", preferedStyle: .Alert)
return false
}
}
else {
return true
}
return false
}
This is some of my code from the "index" viewController where I need to tackle validation.
As you can see all of my viewControllers have a property named tempMedecine. I pass it around and update the data if needed.
The problem is that self.tempMedecine.dosageQuantityType returns nil in the shouldPerformSegueWithIdentifier method but isn't returning nil in the unwindToViewController method.
I figured there could be two instances of my TempMedecine object, but that's not the case. I also thought there might be a problem with the way I pass the tempMedecine variable between my viewControllers but the property tempMedecine.name is effectively transfered, the only difference is that this property is set in the same viewController where I want to implement validation :
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
self.tempMedecine.name = textField.text
return true
}
It's really looking like I'm working with two different scope. As soon as I leave the unwindToViewController method, I would get back to another scope where the tempMedecine variable isn't updated.
But the weird part is when I use a simple variable like xValue. If I update its value in the unwindToViewController method I get the correct value in shouldPerformSegueWithIdentifier
What am I missing? Thanks for your help.
Okay, I fixed it. What happened was that I implemented some code to reset the dataSource which is tempMedecine if the user decided to click on the Back Button in the navigation bar :
if self.isMovingToParentViewController() {
tempMedecine?.dosageQuantity = nil
tempMedecine?.dosageQuantityType = nil
}
The thing is, I never thought the issue could come from this as I can use the tempMedecine data to set the value in my tableView after unwinding to the index viewController but I totally missed the part when object are passed my reference.

ios swift: #IBAction handling to avoid segue

I have 2 views, first to enter some data and second to fill in more details based on the data the user entered on the first view.
Therfore I have a function like:
#IBAction func addNewCardSet(sender: AnyObject) { ...
let givenCardSetName = newCardSetName.text
if givenCardSetName != "" {
... save routine ...
}else{
updateLabel("Please fill in a name")
}
I also added a segue to the addNewCardSet Button to do a segue to the second view. What happens now is that if the user doesn't enter a name, I can see the message label saying "Please fill in the name" but one little moment later the segue takes place and send the user to the next view without any saved data...
What can I do to "allow" the segue only, if my save method took place with no errors and it is the time to do the segue?
You can implement shouldPerformSegueWithIdentifier method:
override func shouldPerformSegueWithIdentifier(identifier: String?, sender: AnyObject?) -> Bool {
if let ident = identifier {
if ident == "YOUR IDENTIFIER HERE" {
let givenCardSetName = newCardSetName.text
if givenCardSetName != "" {
return true
}else{
return false
}
}
}
return true
}

Resources