Performing Segue with Prepare after validation check - ios

I have a form that needs to be validated before performing a segue and also sending over the data to the next view controller. For now I'm just checking to see if all text fields are filled in:
#IBAction func startBtn(_ sender: Any) {
if(idInput.text == "" || dob1Field.text == "" || dob2Field.text == "" || dob3Field.text == ""){
print("no text")
}
}
My idea is, when the start button is pressed, check if all fields are filled, if they are use prepare to segue to the next VC and send the data.
I'm struggling to understand how to do this, I linked the start button on the storyboard to the VC and gave it an identifier mainUse since it is going to the mainUseController
Here is the prepare function:
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if segue.identifier == "mainUse"{
let vc = segue.destination as! mainUseController
}
}
The part I'm struggling to understand is how to call the prepare function once the check is done and succeeded. Thanks.

I linked the start button on the storyboard to the VC and gave it an
identifier mainUse since it is going to the mainUseController
Well here is the issue: your segue seems to be generated by dragging from the button to the destination view controller, don't do this because the segue will performed regardless of what's implemented in the button action. Instead drag from the view controller itself (but not from the button) to the destination view controller:
control + drag from the view controller to the destination controller
At this point, tapping the button won't forcibly navigates you to the destination view controller. Next what you should do is to perform the segue if the conditions are met, by calling performSegue(withIdentifier:sender:) method:
#IBAction func startBtn(_ sender: Any) {
if idInput.text == "" || dob1Field.text == "" || dob2Field.text == "" || dob3Field.text == "" {
print("no text")
return
}
// just don't forget to assign the segue identifier as 'mainUse'...
performSegue(withIdentifier: "mainUse", sender: nil)
}

Use the following code to perform the segue, first check if all condition fulfill then fire segue else display error message accordingly.
#IBAction func startBtn(_ sender: Any) {
if(idInput.text == "" || dob1Field.text == "" || dob2Field.text == "" || dob3Field.text == ""){
print("no text")
//Show alert message here
}else{
self.performSegue(withIdentifier: "mainUse", sender: self)
}
}

prepare(for segue:) method is called by the ViewController delegate, you should avoid putting code there that you need to trigger.
What you can call for segueing is performSegue(withIdentifier:sender:)
More in:
https://developer.apple.com/documentation/uikit/uiviewcontroller/1621413-performsegue
IMPORTANT CAVEAT
Remember that your outlets are nil when segueing, if you want to assign or pass data to the receiver VC, create a strong property and assign that value before you segue but after the VC instantiation. Labels, texts, etc won't receive any data until they're draw.
If you need the entries from the text fields to be sent over to the next view controller, create a placeholder property and assign it during the segueing process.
You have a nice day!

#IBAction func startBtn(_ sender: Any) {
if idInput.text?.isEmpty ?? true || dob1Field.text?.isEmpty ?? true || dob2Field.text?.isEmpty ?? true || dob3Fieldtext?.isEmpty ?? true { print("some textField is empty") return }
dispatch_async(dispatch_get_main_queue()) { [unowned self] in
self.performSegueWithIdentifier("YourIdentifier", sender: self)
}
}
If you are using navigation controller:
#IBAction func startBtn(_ sender: Any) {
if idInput.text?.isEmpty ?? true || dob1Field.text?.isEmpty ?? true || dob2Field.text?.isEmpty ?? true || dob3Fieldtext?.isEmpty ?? true { print("some textField is empty") return }
if let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "YourIdentifier") as? NextViewController{
if let navigator = navigationController {
navigator.pushViewController(viewController, animated: true)
}
}
}

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
}

ShouldPerformSegue Fires Twice

I have currently come across a weird problem. I have a storyboard which has viewController A with a button. From that button, I have created a segue in storyboard to viewController B. When the button is clicked, a segue is fired.
The button is a Login button, so I need to validate the login details before the segue is performed.
In ViewController A, when the button is pressed, I have following code:
#IBAction func SignInButtonPressed(_ sender: Any) {
guard let email = username.text , username.text != "" else {
return self.successLogin = false
}
guard let pass = password.text , password.text != "" else {
return self.successLogin = false
}
AuthService.instance.loginUser(email: email, password: pass) { (success) in
if success {
self.successLogin = true
} else {
self.displayAlertView(title: USER_LOGIN_FAILED_TITLE, message: USER_LOGIN_FAILED_MESSAGE)
}
}
}
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if successLogin != true && identifier == "homeSegue" {
self.displayAlertView(title: USER_LOGIN_FAILED_TITLE, message: USER_LOGIN_FAILED_MESSAGE)
return false
} else {
return true
}
}
But with this, the first click shows the alert in shouldPerformSegue, and then a second click allows the login.
Any suggestions would be helpful on how can I fix this.
Thanks
You should perform the segue inside your AuthService success closure callback, you should also remove the unwind segue from the button, the button must only execute the login logic, if the login went well then the segue must be performed but not before
AuthService.instance.loginUser(email: email, password: pass) { (success) in
if success {
self.successLogin = true
self.performSegue(withIdentifier: "homeSegue", sender: self)
} else {
self.displayAlertView(title: USER_LOGIN_FAILED_TITLE, message: USER_LOGIN_FAILED_MESSAGE)
}
}
should work now
You can run segue programmatically.
performSegue(withIdentifier: "homeSegue", sender: self)
If you need to perform custom logic, moreover postpone navigation, it is better to bind button to the action, and trigger segue you need from it.

swift conditionally call unwind segue using shouldPerformSegue

I have two view controllers: Step3VC (we'll call this 'A') and Step3AddJobVC (we'll call this 'B'). I'm trying to validate some data on 'B' before performing an unwind segue back to 'A'.
'B' takes some user input, and I want to verify that the user input is not duplicate. The user is making a list of chores, and so duplicate names won't work. When the user taps 'save', the unwind segue performs, and the data is added to an array.
Here's the problem: the array is on 'A', but the validation needs to happen on 'B' before 'A' gets called. How do I do that?
What I've tried:
I've tried using shouldPerformSegue in 'B', but the array comes back blank []. So that's no good. Here's the code from 'B':
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
print("identifier is: ", (identifier))
print("sender is: ", (sender)!)
let newVC = Step3VC()
print(newVC.dailyJobs)
return false
}
So then I tried putting the validation into 'A' during the unwind segue...
#IBAction func unwindToStep3VC(sender: UIStoryboardSegue) {
let sourceVC = sender.source as! Step3AddJobVC
let updatedJob = sourceVC.job
// check for duplicate names
for name in dailyJobs {
print(name.name)
if name.name.lowercased() == (sourceVC.jobTextField.text?.lowercased()) { // check to see if lowercased text matches
print("error")
// call alert function from sourceVC
sourceVC.duplicateNameCheck()
return
}
}
if let selectedIndexPathSection = jobsTableView.indexPathForSelectedRow?.section { // if tableview cell was selected to begin with
// Update existing job
if selectedIndexPathSection == 0 {
let selectedIndexPathRow = jobsTableView.indexPathForSelectedRow
dailyJobs[(selectedIndexPathRow?.row)!] = updatedJob!
jobsTableView.reloadData()
} else if selectedIndexPathSection == 1 {
let selectedIndexPathRow = jobsTableView.indexPathForSelectedRow
weeklyJobs[(selectedIndexPathRow?.row)!] = updatedJob!
jobsTableView.reloadData()
}
} else {
// Add a new daily job in the daily jobs array
let newIndexPath = IndexPath(row: dailyJobs.count, section: 0)
dailyJobs.append(updatedJob!)
jobsTableView.insertRows(at: [newIndexPath], with: .automatic)
}
}
...but it gave the error:
popToViewController:transition: called on <ToDo_App.SetupNavController 0x7fcfd4072c00> while an existing transition or presentation is occurring; the navigation stack will not be updated.
If I pull out the 'if' validation code, the unwind segue works properly. The data is transferred and does the right thing. The problem is that if the user enters duplicate entries, I can't figure out how to stop them.
This is my code for checking if user input is duplicate:
// check for duplicate names
for name in dailyJobs {
print(name.name)
if name.name.lowercased() == (sourceVC.jobTextField.text?.lowercased()) { // check to see if lowercased text matches
print("error")
// call alert function from sourceVC
sourceVC.duplicateNameCheck()
return
}
}
What am I missing? Is there a better way to do this? How do I call the variables from 'A' while I'm in 'B' to perform my validation BEFORE the unwind segue is called / performed?
You are trying to validate the things in shouldPerformSegue which is the right place, the thing which you are doing wrong is recreating new object of Step3VC and trying to access dailyJobs which is never set with value.
let newVC = Step3VC()
print(newVC.dailyJobs)
What you have do is pass dailyJobs form VC A to VC B while presenting VC B and then check if the data is duplicate or not in shouldPerformSegue.
Your code have to look like:
class VCA: UIViewController {
var dailyJobs = getDailyJobsFromServer()
#IBAction segueToVCB(sender: UIButton) {
let sb = UIStoryboard(name: "Main", bundle: nil)
let vcB = sb.instantiateViewController(withIdentifier: "VCB") as! VCB
vcB.dailyJobs = dailyJobs
self.present(vcB, animated: true, completion: nil)
}
}
class VCB: UIViewController {
var dailyJobs: //DataType
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
//Here you do comparision with dailyJobs
if dailyJobs == userInput {
}
return false
}
}

How can stop to jump next view controller? (swift)

I am using segue to send data from one viewController to destinationViewController. I set button and use push segue to connects to next view controller. In the first view, I set a boolean to check the images is or not empty. If there is any empty, it will pop up alert to remind users. If success, it will send the images to next view controller.
Although I can transmit the images and pop up the alert, it also jump to next view controller if there is any empty. Moreover, the alert is popped after jumping to next view controller, not in the first view controller
Now, here is the code
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if Jumpsuit.isHidden == false && self.Hair.image != nil && self.Jumpsuit.image != nil && self.Shoes.image != nil {
let DestViewController : ChooseBGViewController = segue.destination as! ChooseBGViewController
DestViewController.imgHair = Hair.image
DestViewController.imgJumpsuit = Jumpsuit.image
DestViewController.imgShoes = Shoes.image
DestViewController.dressingtype = 1
} else if Jumpsuit.isHidden == false && self.Hair.image == nil || self.Jumpsuit.image == nil || self.Shoes.image == nil {
let alert = UIAlertView()
alert.title = "No Image"
alert.message = "Please check again"
alert.addButton(withTitle: "OK")
alert.show()
}
}
How can I stop jumping to next view controller and pop up the alert in first view controller. Please help!
You can use shouldPerformSegue(withIdentifier:sender:) for that and return Bool value according your condtion.
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if Jumpsuit.isHidden == false && self.Hair.image == nil || self.Jumpsuit.image == nil || self.Shoes.image == nil {
//Show UIAlertController instead of UIAlertView it is deprecated
return false
}
return true
}
When the user tapped the button you would use the IBAction that will first check what you need (the image..) and then you could use 2 approaches:
You can now use performSegue which will trigger the known prepareForSegue and use the segue you created
You can use the instantiateViewController(withIdentifier: approach as follows (and don't forget to disconnect the segue):
let nextVC = storyboard?.instantiateViewController(withIdentifier: "YourViewControllerID")
self.present(nextVC!, animated: true, completion: nil)
or if you have navigation controller:
self.navigationController?.pushViewController(nextVC!, animated: true)

How to send Parse object field from one class to another?

I want to make such thing:
On one ViewControleer I'm making a query to Parse.com, where I'm sending objects fields to Label.Text. By clicking one button objects randomly changes, by clicking another one- next ViewController is opening. Just imagine Tinder - on the first VC I swiping girls, on the new one chat is opening, with the girl's name in the head of the NavigatorItem
So I want to send Object Field "Name" that I'm using in that view to another without other query.
I don't know, whether I can do it via segue, or protocol. Can U somehow help me with implementation?
here is the code of my random function
func retriveJobData() {
var query: PFQuery = PFQuery(className: "Jobs")
query.getObjectInBackgroundWithId("AUeuvj0zk2") {
(newJobObject: PFObject?, error: NSError?) -> Void in
if error == nil && newJobObject != nil {
println(newJobObject)
if let newJobObject = newJobObject {
self.PrcieTextField.text = newJobObject["jobPrice"] as? String
self.DateTextField.text = newJobObject["jobDate"] as? String
self.DescriptionTextField.text = newJobObject["jobDescription"] as? String
}
} else {
println(error)
}
}
}
I want to send newJobObject["jobName"] to NavigatorItemName of another ViewController
you can override prepareForSegue for this purpose:
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "yourSegueIdentifier") {
// pass data to next view
}
}
Assuming you have some method that triggers a push to the new viewController and that you're using the storyboard, call performSegue using the identifier you set up in the storyboard
#IBAction func buttonPressed(sender: UIButton!) {
performSegueWithIdentifier("identifier", sender: nil)
}
Then override prepareForSegue and pass in the string
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "identifier" {
let controller = segue.destinationViewController as! ViewController
controller.jobName = someWayThatYouRetrieveNewJobObjectName
}
Then in ViewController ensure you have a property for jobName
var jobName:String! //declare this as an optional instead if needed
And set the navigation title
navigationItem.title = jobName

Resources