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

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)

Related

Performing Segue with Prepare after validation check

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

SWIFT 4 Present viewcontroller

I am trying to building a app using the master details template.
in the Master view controller I added a button called Catalogue : this button showing a tabbar controller called Catalogue.
I don't use Segue to show the catalogue, I use the code below to show the tab controller
From Master form I called the Tabbar controller :
#IBAction func Btn_Catalogue(_ sender: Any) {
let AddCatalogueVC = self.storyboard?.instantiateViewController(withIdentifier: "CatalogueVC") as! CatalogueVC
present(AddCatalogueVC, animated: true, completion: nil)
}
From CategorieVC I use the code below to show
#IBAction func Btn_AddCategorie(_ sender: Any) {
self.Mode = "New"
let AddCategorieViewController = self.storyboard?.instantiateViewController(withIdentifier: "AddCategorieVC") as! AddCategorieVC
present(AddCategorieViewController, animated: true, completion: nil)
}
I dismiss the AddCategorieVC using the code below
#IBAction func Btn_Save(_ sender: Any)
{
if self.Txt_CategorieName.text != ""{
self.Mysave = true
self.MyCategorieName = self.Txt_CategorieName.text!
self.dismiss(animated: true, completion: nil)
}
}
I have unwind SEGUE from Save button to a function in categorieVC
#IBAction func FctSaveCategories(_ sender: UIStoryboardSegue) {
let sendervc = sender.source as! AddCategorieVC
if self.Mode == "New" && sendervc.Mysave == true { // Mode insert
let MyCategories = TblCategorie(context: Mycontext)
MyCategories.categorie_Name = sendervc.MyCategorieName
do {
try Mycontext.save()
} catch {
debugPrint ("there is an error \(error.localizedDescription)")
}
}
}
The problem is when I hit the save button in categorieVC the catalogueVC is also dismissing at the same time returning me to the master control.
I am almost sure that the problem came from the Unwind segue but I don't know why.
Update: I activate the Cancel button in AddCategorieVC with the code below
self.dismiss(animated: true, completion: nil)
and when I clicked on it only the AddCategorieVC is being dismissed and I go back to CatalogueVC. The difference between the save button and the Cancel Button is only the UNWIND segue on the Save Button.
And when I add UnWIND segue to the cancel Button (just to test the behavior) it took me back to the master form instead CatalogueVC.
How can I solve that?
And yesss I found it
It look like that unwind segue automaticly handled dismiss contrĂ´le
So all I need to do is remove the dismiss code from the save button this way the unwind segue will took me back to catalogueVC.
.

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

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.

Resources