unwind segue cancel upon textbox value equal to nil - ios

I would like to cancel an unwind segue if the textbox values are equal to nil.
below is the current code. I used storyboard to connect the save button (saves values of text into database). I was thinking about using poptorootviewcontroller to ensure the textbook has values before performing segue. is their any way to do this with current code?
code inside of mainView to unwind back to-
#IBAction func unwind(_ segue: UIStoryboardSegue){
print("Back in TableView")
}
button on childView perform unwind segue-
#IBAction func saveAddress(_ sender: UIButton) {
// save values to dictionary save dictionary to firebase under user, uid, addresses
// uialert controller if fields are not completed do not allow segue if fields are not complete
// perform segue to addresstableview
let addy1: String = address1Txt.text!
let addy2: String = address2Txt.text!
let aptNum: String = aptTxt.text!
let city: String = cityTxt.text!
let state: String = stateTxt.text!
let zip: String = zipTxt.text!
// add UIAlert controller for address field == nothing
if addy1.isEmpty && aptNum.isEmpty && city.isEmpty && state.isEmpty && zip.isEmpty
{
//add UIAlert Controller DO NOT PERFORM SEGUE IF TRUE
}
Address.sharedInsance.typedNewAddress = addy1 + "," + addy2 + "," + aptNum + "," + city + "," + state + "," + zip
print("address save print",addy1, addy2, aptNum, city, state, zip)
let key = ref.child("address").childByAutoId().key
let setter = false
let addyDict = ["address line 1":addy1,"address line 2":addy2,"apt Number":aptNum,"city":city,"state":state,"zip":zip,"keyID": key, "setter": setter] as [String : Any]
let userID = Auth.auth().currentUser?.uid
let childUpdates = ["/address/\(key)": addyDict]
ref.child("users").child(userID!).updateChildValues(childUpdates)
}

Given that I don't see you performing the segue in your #IBAction, we'll have to presume that you hooked your button up to both an #IBAction and an unwind segue. You should remove the segue from the button's "Connections Inspector" on the last tab on the panel on the right and instead, create an unwind segue from the view controller, itself, to the exit outlet:
You can then select that segue in the panel on the left, click on the "attributes inspector" for that segue, give it a storyboard identifier:
Then you can programmatically perform the segue, using that storyboard identifier, only if appropriate in your remaining #IBAction, e.g.
#IBAction func didTapDoneButton(_ sender: Any) {
// if the fields are `nil` or have a length of zero characters, show warning
// and just `return` without ever performing unwind segue
guard let firstName = textFieldFirstName.text?.trimmingCharacters(in: .whitespacesAndNewlines),
let lastName = textFieldLastName.text?.trimmingCharacters(in: .whitespacesAndNewlines),
firstName.count > 0, lastName.count > 0 else {
let alert = UIAlertController(title: nil, message: "Please fill in all fields", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
present(alert, animated: true)
return
}
// do something with firstName and lastName
save(firstName: firstName, lastName: lastName)
// now perform unwind segue
performSegue(withIdentifier: "unwindHome", sender: sender)
}
Having shown you that, I believe the more robust approach is to not let the user tap the "done"/"save" button until the required fields are entered. Bottom line, rather than handling the error situation, design a UI where it is impossible to make such an error.
So, some of the key aspects of that would include:
Dim/disable the "done"/"save" button;
Set the "placeholder text" for the text fields to indicate that the field is required. E.g. a placeholder of "First name (required)" so that the user knows it's required.
Add editingChanged method for the various text fields that enables/disables the "done" button as appropriate:
#IBAction func editingChanged(_ sender: UITextField) {
if let firstName = textFieldFirstName.text?.trimmingCharacters(in: .whitespacesAndNewlines),
let lastName = textFieldLastName.text?.trimmingCharacters(in: .whitespacesAndNewlines),
firstName.count > 0, lastName.count > 0 {
doneButton.isEnabled = true
} else {
doneButton.isEnabled = false
}
}
Obviously hook this up to the "editingChanged" action for all of the relevant text fields.
This yields a "Done" button (the one in the upper right corner of this demo) that is disabled until there is at least some text in all of the text fields:
Bottom line, rather handling the error, prevent it in the first place.

You should override shouldPerformSegue method and return false if the textbook fields are empty.
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
}

Related

Swift: How to display two buttons that have been pressed on a label

I am trying to get buttons (they are numbers) when pressed to show two digits in the label, currentChannelLabel, I created. I have been messing around with this and I can either get one number to show, or I get two of the same number to show. How do I fix my code so I get one number to show, then a second number? After this, if another button is selected, it will show the first number again and then the second. For example:
(button 1 is pressed)
currentChannelLabel: 1
(button 2 is pressed)
currentChannelLabel: 12
(button 3 is pressed)
currentChannelLabel: 3
(button 4 is pressed)
currentChannelLabel: 34
here is my code:
#IBAction func channelNumbers(_ sender: UIButton) {
var number = ""
if let pressed = sender.currentTitle {
number = "\(pressed)"
currentChannelLabel.text = number
if let pressed2 = sender.currentTitle{
number += "\(pressed2)"
currentChannelLabel.text = number
}
}
}
The problem with your code is that pressed2 will always be equal to pressed, because it's not going to change within the body of the function — instead, when the second button is pressed, the function is called again.
Here's some code that should do what you want:
#IBAction func channelNumbers(_ sender: UIButton) {
guard let number = currentChannelLabel.text else {
currentChannelLabel.text = sender.currentTitle
return
}
currentChannelLabel = (number.count == 1 ? number : "") + (sender.currentTitle ?? "")
}
Your code doesn't make much sense. You have two nested if let statements. Either the first will fail, or both will succeed.
You need something like this:
#IBAction func channelNumbers(_ sender: UIButton) {
if let pressed = sender.currentTitle {
let oldLabelText = currentChannelLabel.text ?? ""
currentChannelLabel.text = oldLabelText + pressed
}
}
The string you get in pressed will be the title of the button that was pressed. Then you append that to previous text from the label, and put the result back into currentChannelLabel.text.
(Note that storing your string in currentChannelLabel.text is not ideal. You should really keep a string var in your view controller, append to that, and copy that string into currentChannelLabel.text. (View objects should not hold state. They should display info to the user and collect input from the user.)

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

Cannot check reference equality of functions; operands here have types 'UIBarButtonItem' and '(UIBarButtonItem) -> ()'

I am trying this Apple tutorial but I found an error. Can someone help me?
Disable Saving When the User Doesn't Enter an Item Name
What happens if a user tries to save a meal with no name? Because the meal property on MealViewController is an optional and you set your initializer up to fail if there’s no name, the Meal object doesn’t get created and added to the meal list—which is what you expect to happen. But you can take this a step further and keep users from accidentally trying to add meals without a name by disabling the Save button while they’re typing a meal name, and checking that they’ve specified a valid name before letting them dismiss the keyboard.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
// Configure the destination view controller only when the save button is pressed.
guard let button = sender as? UIBarButtonItem, button === saveButton else {
if #available(iOS 10.0, *) {
os_log("The save button was not pressed, cancelling", log: OSLog.default, type: .debug)
} else {
// Fallback on earlier versions.
}
return
}
let name = nameTextField.text ?? ""
let photo = photoImageView.image
let rating = ratingControl.rating
// Set the meal to be passed to MealTableViewController after the unwind segue.
meal = Meal(name: name, photo: photo, rating: rating)
}
You probably have a problem with the saveButton property declaration. It should be typed as UIBarButtonItem(and not (UIBarButtonItem) -> Void as your error shows).
To be safe, try repeating the To connect the Save button to the MealViewController code step from the Apple tutorial.

move data to another view controller, but the 2nd view controller load first before 1st view controller

i want to make when user login in the login view controller, the email saved and display to slide out menu view controller, but the slide out menu displayed first when build and run. So the variable contains nothing because user haven't login yet in login view controller.
im using this to declare the text in text field is email fill
let emailFill:String = self.emailTextfield.text!
and use
self.labelContainEmail = emailFill
the labelContainEmail is a global variable at top
var labelContainEmail: String = ""
i'm using prepare for segue like this to move data
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
if segue.identifier == "yolo"
{
let vc = segue.destinationViewController as! SlideOutMenu
vc.labelContainEmail2 = labelContainEmail
print(vc.labelContainEmail2) // this don't print anything, but when i print under `self.labelContainEmail = emailFill` it printed out the email
}
}
in the slideOutMenu.swift, i have this var at top
var labelContainEmail2: String = ""
and try to change the label text with
labelEmail.text = labelContainEmail2
in the view did Load
what should i do? please go easy, i'm new to swift
using prepare for segue store email in NSUserDefaults
NSUserDefaults.standardUserDefaults().setObject(labelContainEmail , forKey: "email") // save user email
in the slideOutMenu.swift and try to change the label text with in the view did Load
if (NSUserDefaults.standardUserDefaults().objectForKey("email")) == nil
{
NSUserDefaults.standardUserDefaults().setObject("" , forKey: "email")
}
else
{
let emailStr = NSUserDefaults.standardUserDefaults().objectForKey("email") as! String
}
You can use on the top this line
** Second View
class SlideOutMenu: UIViewController {
var labelContainEmail: String?
//your code here
}
Where you are going from first view to second view.
**Use First View
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
if segue.identifier == "yolo"
{
let vc = segue.destinationViewController as! SlideOutMenu
vc.labelContainEmail2 = labelContainEmail
print(vc.labelContainEmail2) // this don't print anything, but when i print under `self.labelContainEmail = emailFill` it printed out the email
}
}
i hope it will help you other wise you can use Model Class also better way for pass data.

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