How to add two numbers in Swift only when both of the text fields are filled in - ios

I'm trying to add two numbers in Swift 5, and I want to add some error checks. I don't want to make it possible for a user to click on the plus button if both of the text fields are not filled in. I tried with the if state below but it did not work.
the whole function:
#IBAction func sum(_ sender: Any) {
let one = input1.text
let oneInt = Int(one!)
let two = input2.text
let twoInt = Int(two!)
let total = oneInt! + twoInt!
label.text = "\(total)"
if(input2.text == nil){
addBtn.isEnabled = false
}
if(input1.text == nil){
addBtn.isEnabled = false
}
}

Try to use guard like this. If your input field does not contain any value that field return blank string and when you try to get integer value from that string it will return nil and your add button will be disable.
#IBAction func sum(_ sender: Any) {
guard let text1 = input1.text, let intValue1 = Int(text1) else {
addBtn.isEnabled = false
return
}
guard let text2 = input2.text, let intValue2 = Int(text2) else {
addBtn.isEnabled = false
return
}
label.text = "\(intValue1 + intValue2)"
}

A nice and simple way is to addTarget to your textFiels. This will enable you to handle the events on the text field. In this scenario we'll use .editingChanged and use a single selector to achieve our goal:
What we'll do : We will listen for when someone types something in the textfield. Whenever a text changed was made, we'll check to see if all the textfields was populated and then if it was we enable the sum button.
A small controller sample :: Make sure to read the comments to understand the code faster
import UIKit
class ViewController: UIViewController {
#IBOutlet var textfield1: UITextField!
#IBOutlet var textfield2: UITextField!
#IBOutlet var sumButton: UIButton!
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
sumButton.isEnabled = false /// Disable the button first thing
[textfield1, textfield2].forEach {
$0.addTarget(self, action: #selector(editingChanged(_:)), for: .editingChanged) /// add targets to handle the events (in your case it listens for the 'editingChanged' event )
}
}
#objc func editingChanged(_ textField: UITextField) {
/// Here we just loop through all our textfields
for each in [textfield1, textfield2] {
if let text = each?.text { /// Just make sure the textfields text is not nil
if text.count < 1 {
// If the textfiels text has no value in, then we keep the button disabled and return
sumButton.isEnabled = false
return
}
} else {
/// Else if the text field's text is nill, then return and keep the button disabled
sumButton.isEnabled = false
return
}
}
sumButton.isEnabled = true /// If the code reaches this point, it means the textfields passed all out checks and the button can be enabled
}
#IBAction func sum(_ sender: Any) {
let one = textfield1.text!
let two = textfield2.text!
guard let oneInt = Int(one), let twoInt = Int(two) else {
print("Whatever was in that text fields, couldn't be converted to an Int")
label.text = "Be sure to add numbers."
return
}
let total = oneInt + twoInt
label.text = "\(total)"
}
}

textfields are not nil but empty strings. so make your comparison like :
if input1.text == "" {
// do your check here
}

Seems like you want to start with addBtn.isEnabled = false then update it whenever the user enters two valid integers into the text fields, i.e. Int(input1.text ?? "") != nil && Int(input2.text ?? "") != nil. You can do this by adding a target to your textfields (input1 and input2) for .editingChanged events. For example, if you're doing this in a UIViewController, you can do this in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
addBtn.isEnabled = false
input1.addTarget(self, action: #selector(textFieldDidEdit(_:)), for: .editingChanged)
input2.addTarget(self, action: #selector(textFieldDidEdit(_:)), for: .editingChanged)
}
Where textFieldDidEdit(_:) action looks like:
#objc func textFieldDidEdit(_ sender: UITextField) {
addBtn.isEnabled = Int(input1.text ?? "") != nil && Int(input2.text ?? "") != nil
}
Finally your sum function becomes:
#IBAction func sum(_ sender: UIButton) {
guard let oneInt = Int(input1.text ?? ""), let twoInt = Int(input2.text ?? "") else {
return
}
let total = oneInt + twoInt
label.text = "\(total)"
}
As all of the number validation has moved to the textFieldDidEdit(_:) function.

Related

Detect when a button is pressed outside of the IBAction in Swift?

I'm a beginner programmer making my first real app, a calculator in Swift.
It's mostly working, but one issue I'm running into is how it displays numbers after pressing one of the operator buttons. Currently, whenever an operator button is pressed, I have it set the label at the top of the calculator to "0". But on actual calculators, this top display won't change until another number button is pressed.
If I don't reset the display to 0, then any number buttons that are pressed will add to the current text at the top, and mess up the equation that the calculator will have to do (i.e. 2+2 displays 22, and the solution it displays is 22+2=24)
I'm wondering if it's possible to detect when one of the number buttons is pressed (listed in my code as the intButtonPressed IBAction) outside of the intButtonPressed function? That way I can keep the top label the same until the user starts inputting more text, then I can set it to 0 to prevent the calculator from breaking.
Any other possible (better) solutions would be welcome as well
Here's my code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var topLabel: UILabel!
var num1 = Double()
var solution = Double()
var op = String()
#IBAction func intButtonPressed(_ sender: UIButton) {
if topLabel.text == "0" {
topLabel.text = sender.currentTitle
}
else if topLabel.text == String(solution) {
num1 = Double(topLabel.text!)!
solution = 0.00
topLabel.text = sender.currentTitle
// Basically stops user from adding to solution?
// Also stores solution as num1 and clears solution field
}
else {
topLabel.text = topLabel.text! + sender.currentTitle!
// Keep typing
}
if (topLabel.text?.count)! > 12 {
topLabel.text = "Error"
}
else {
return
}
// if its greater than 12 characters, display "error"
}
#IBAction func clearButtonPressed(_ sender: UIButton) {
num1 = 0.00
solution = 0.00
topLabel.text = "0"
}
#IBAction func opButtonPressed(_ sender: UIButton) {
if sender.currentTitle == "=" {
equals()
}
else {
op = sender.currentTitle!
num1 = Double(topLabel.text!)!
topLabel.text = "0"
}
// Need it to display num1 until I press another button, then I need it to only display that text.
}
func equals() {
switch op {
case "+":
add()
case "-":
subtract()
case "×":
multiply()
case "÷":
divide()
default:
print(Error.self)
}
}
func add() {
solution = num1 + Double(topLabel.text!)!
topLabel.text = String(solution)
}
func subtract() {
solution = num1 - Double(topLabel.text!)!
topLabel.text = String(solution)
}
func multiply() {
print("topLabel = ", topLabel.text!)
solution = num1 * Double(topLabel.text!)!
print("solution = ", solution)
topLabel.text = String(solution)
}
func divide() {
solution = num1 / Double(topLabel.text!)!
//answer()
}
}
Update for anyone who finds this in the future: I've solved the issue, and realized that I wasn't very clear with what I wanted it to do. I solved the problem simply by adding a condition to the if/else statement in the inButtonPressed function that detects if the topLabel is 0. By rewriting that to detect if the topLabel is 0 OR the same as num1, and then changing the else statement in the opButtonPressed function to set topLabel to String(num1), the program does exactly what I want. Here's the rewritten code:
#IBAction func intButtonPressed(_ sender: UIButton) {
if topLabel.text == "0" || topLabel.text == "0.0" || topLabel.text == String(num1){
dotPressed = false
// Resets dotPressed whenever the top is 0 or num1 and an int button is pressed
topLabel.text = sender.currentTitle
}
else if topLabel.text == String(solution) {
num1 = Double(topLabel.text!)!
solution = 0.00
topLabel.text = sender.currentTitle
// Basically stops user from adding to solution?
// Also stores solution as num1 and clears solution field
}
else {
topLabel.text = topLabel.text! + sender.currentTitle!
}
}
#IBAction func opButtonPressed(_ sender: UIButton) {
if sender.currentTitle == "=" {
equals()
}
else {
op = sender.currentTitle!
num1 = Double(topLabel.text!)!
topLabel.text = String(num1)
}
// Successfully displays num1 until I press another button, then only displays that text.
I also added a separate function to handle decimals, and I had to update the code in there as well to keep that working.

Update output as user types in textfield

I have code to sum four text fields and output the total in a label. Currently the code sums the fields after finishing editing, that is, selecting another text field. Is there a way to sum the text fields as the user types?
#IBAction func TankFuelChanged(_ sender: Any) {
let leftMainTankQuantityValue = Int(leftMainTankQuantity.text ?? "") ?? 0
let rightMainTankQuantityValue = Int(rightMainTankQuantity.text ?? "") ?? 0
let auxTankQuantityValue = Int(auxTankQuantity.text ?? "") ?? 0
let tailTankQuantityValue = Int(tailTankQuantity.text ?? "") ?? 0
let total = leftMainTankQuantityValue + rightMainTankQuantityValue + auxTankQuantityValue + tailTankQuantityValue
totalFuelLoad.text = "\(total)"
What you are looking for is an event triggered when text field changes. You can drag an action from storyboard or you can add them programmatically by using addTarget similar to UIButton but need to use event editingChanged. Check the following code:
var allTextFields: [UITextField] {
return [leftMainTankQuantity, rightMainTankQuantity, auxTankQuantity, tailTankQuantity]
}
override func viewDidLoad() {
super.viewDidLoad()
allTextFields.forEach { $0.addTarget(self, action: #selector(onTextFieldChange), for: .editingChanged) }
}
#objc private func onTextFieldChange() {
updateResult()
}
private func updateResult() {
let strings: [String] = allTextFields.compactMap { $0.text } // Will remove all nil texts
let integers: [Int] = strings.compactMap { Int($0) } // Will remove all non-integer texts
let sum = integers.reduce(0, { $0 + $1 }) // Will compute a sum
print(sum) // TODO: update your result here
}
A method must be marked #objc because of the #selector next to that I hope code speaks for itself.

Empty array causing my app to crash in Swift

I keep coming across this error "fatal error: Index out of range", after researching this I am still unsure of exactly how to fix this issue. To give context I have started off with an empty array var playersArray = [UITextField]() so that users can enter their names in order to play the game. I then make sure that the user has or has not entered a value into the text field for each name slot
if let player1Name = name1.text, !player1Name.isEmpty
{ playersArray.append(name1)
} else {
print("Player 1 Empty")
If the player has entered a value into that textfield then ill append that value to the array.
The issue I have is that if I run the game and no user has entered a name into any of the 10 textfields then the game will crash. Im assuming this is because the array is empty?
The error appears on this line where I randomize the names used for the game with the number of elements in the array:
let RandomPlayer = playersArray[Int(arc4random_uniform(UInt32(playersArray.count)))]
I assume if the array is empty then .count will not work?
How can I make sure the game wont crash if the array is empty?
CODE:
var playersArray = [UITextField]()
override func viewDidLoad() {
super.viewDidLoad()
textColor()
question1View.isHidden = true
questionLabel.transform = CGAffineTransform(rotationAngle: CGFloat.pi / 2)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// Alert message on startup
func alertMessageOnStartUp(){
let alert = UIAlertController(title: "Warning!", message: "Please drink responsibly. By continuing, you agree that you are responsible for any consequences that may result from BottomsUp.", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Agree", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
// Dismiss keyboard when tapped outside the keyboard
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
// Dimiss keybaord when return button is tapped
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
name1.resignFirstResponder()
name2.resignFirstResponder()
name3.resignFirstResponder()
name4.resignFirstResponder()
name5.resignFirstResponder()
name6.resignFirstResponder()
name7.resignFirstResponder()
name8.resignFirstResponder()
name9.resignFirstResponder()
name10.resignFirstResponder()
return(true)
}
//randomise background colour of each question page
func getRandomBackgroundColor() -> UIColor{
let randomRed:CGFloat = CGFloat(drand48())
let randomGreen:CGFloat = CGFloat(drand48())
let randomBlue:CGFloat = CGFloat(drand48())
return UIColor(red: randomRed, green: randomGreen, blue: randomBlue, alpha: 1.0)
}
func textColor(){
name1.textColor = UIColor.white
name2.textColor = UIColor.white
name3.textColor = UIColor.white
name4.textColor = UIColor.white
name5.textColor = UIColor.white
name6.textColor = UIColor.white
name7.textColor = UIColor.white
name8.textColor = UIColor.white
name9.textColor = UIColor.white
name10.textColor = UIColor.white
}
#IBAction func playButton(_ sender: Any) {
alertMessageOnStartUp()
if let player1Name = name1.text, !player1Name.isEmpty
{ playersArray.append(name1)
} else {
print("Player 1 Empty")
}
if let player2Name = name2.text, !player2Name.isEmpty
{ playersArray.append(name2)
} else {
print("Player 2 Empty")
}
if let player3Name = name3.text, !player3Name.isEmpty
{ playersArray.append(name3)
} else {
print("Player 3 Empty")
}
if let player4Name = name4.text, !player4Name.isEmpty
{ playersArray.append(name4)
} else {
print("Player 4 Empty")
}
if let player5Name = name5.text, !player5Name.isEmpty
{ playersArray.append(name5)
} else {
print("Player 5 Empty")
}
if let player6Name = name6.text, !player6Name.isEmpty
{ playersArray.append(name6)
} else {
print("Player 6 Empty")
}
if let player7Name = name7.text, !player7Name.isEmpty
{ playersArray.append(name7)
} else {
print("Player 7 Empty")
}
if let player8Name = name8.text, !player8Name.isEmpty
{ playersArray.append(name8)
} else {
print("Player 8 Empty")
}
if let player9Name = name9.text, !player9Name.isEmpty
{ playersArray.append(name9)
} else {
print("Player 9 Empty")
}
if let player10Name = name10.text, !player10Name.isEmpty
{ playersArray.append(name10)
} else {
print("Player 10 Empty")
}
question1View.isHidden = false
question1View.backgroundColor = getRandomBackgroundColor()
let RandomPlayer = playersArray[Int(arc4random_uniform(UInt32(playersArray.count)))]
let RandomQuestion = questionArray[Int(arc4random_uniform(UInt32(questionArray.count)))]
questionLabel.text = RandomPlayer.text! + RandomQuestion
}
#IBAction func nextQuestionButton(_ sender: Any) {
question1View.backgroundColor = getRandomBackgroundColor()
let RandomPlayer = playersArray[Int(arc4random_uniform(UInt32(playersArray.count)))]
let RandomQuestion = questionArray[Int(arc4random_uniform(UInt32(questionArray.count)))]
questionLabel.text = RandomPlayer.text! + RandomQuestion
}
}
Breaking this down:
Int(arc4random_uniform(UInt32(playersArray.count)))
This line gets a random number with a minimum value of 0 and a maximum value of the length of the playersArray minus 1.
I'm actually not sure what it does when the argument you pass in is 0, but it doesn't really matter, as we'll see next.
Then you use that random value here:
playersArray[thatRandomNumber]
Because there are no elements in playersArray, no matter what the value is of thatRandomNumber, it's going to be out of bounds.
You probably want something more like this:
let RandomPlayer = <some default value>
if !playersArray.isEmpty {
RandomPlayer = playersArray[Int(arc4random_uniform(UInt32(playersArray.count)))]
}
EDIT
Your latest code still doesn't seem to do anything to prevent indexing into the empty array.
You have:
#IBAction func playButton(_ sender: Any) {
...
let RandomPlayer = playersArray[Int(arc4random_uniform(UInt32(playersArray.count)))]
let RandomQuestion = questionArray[Int(arc4random_uniform(UInt32(questionArray.count)))]
questionLabel.text = RandomPlayer.text! + RandomQuestion
}
You need:
#IBAction func playButton(_ sender: Any) {
...
if playersArray.isEmpty {
// do something about that
} else {
let RandomPlayer = playersArray[Int(arc4random_uniform(UInt32(playersArray.count)))]
let RandomQuestion = questionArray[Int(arc4random_uniform(UInt32(questionArray.count)))]
questionLabel.text = RandomPlayer.text! + RandomQuestion
}
}
playersArray.count for an empty array is 0, so you are trying to access playersArray[0] - but the array is empty, so nothing exists at the 0 index.
You should do something like this:
let randomPlayer: Player
if !playersArray.isEmpty {
randomPlayer = playersArray[Int(arc4random_uniform(UInt32(playersArray.count)))]
} else {
randomPlayer = Player() //create a fallback player
}
Alternatively you could make randomPlayer an optional, rather than providing a fallback value. Depends on your needs for that variable.

Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP,subcode = 0x0)

class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
var userIsInTheMiddleOfTypingNumber: Bool = false
#IBAction func appendDigit(sender: UIButton) {
let digit = sender.currentTitle!
if (userIsInTheMiddleOfTypingNumber) {
label.text = label.text! + digit
}
else {
label.text = digit
userIsInTheMiddleOfTypingNumber = true
}
}
}
I am currently creating a calculator app. For some reason, when I run the simulator and try to input some digits in, the siumulator freezes, and the error of bad_instruction appears on the line:
label.text = digit
How would I fix this situation?
Looks like you forgot to connect the label in the Storyboard with the outlet.
Looks like the currentTitle property is nil which results in a crash. You are force unwrapping the property without any nil check.
#IBAction func appendDigit(sender: UIButton) {
if let digit = sender.currentTitle {
if (userIsInTheMiddleOfTypingNumber) {
label.text = label.text! + digit
}
else {
label.text = digit
userIsInTheMiddleOfTypingNumber = true
}
}
}
OR
It could be the reason specified by #dasdom

How do I update textfield2 based on editing textfield1?

I like to update textfield2 as soon as totalAmount has been edited, I am trying the following:
totalAmount.addTarget(self, action: "changeTextField2:", forControlEvents: UIControlEvents.EditingDidEndOnExit)
func changeTextField2(textField: UITextField) {
print("Total amount: \(totalAmount.text)")
//amount vat - calculate vat based on textField
amountVAT.text = "88"
}
And i am saving the text field values with
func newItem(){
let context = self.context
let ent = NSEntityDescription.entityForName("List", inManagedObjectContext: context)
let nItem = List(entity: ent!, insertIntoManagedObjectContext: context)
let numberFormatter = NSNumberFormatter()
nItem.amountVAT = numberFormatter.numberFromString(amountVAT.text!)
.....more values
nItem.invoiceImage = UIImagePNGRepresentation(imageHolder.image!)
do {
try context.save()
} catch {
print(error)
}
}
func saveValues(sender: AnyObject) {
if nItem != nil {
editItem()
} else {
newItem()
}
dismissVC()
}
This can be done using UITextFieldDelegate Method use this function
1.)
textField1.delegate = self
2.)
func textFieldDidEndEditing(textField: UITextField)
{
if textField == textField1
{
textField2.text = textField1.text
}
}
now its upto you how you change the totalAmount.
Use the delegate function of UITextField
- textFieldDidEndEditing: for your textField1. Once in this function, you can get value of textField1 and you can edit textField2.
Apple Documentation for UITextFieldDelegate
Change the code to this totalAmount.addTarget(self, action: "changeTextField2:", forControlEvents: UIControlEvents.EditingChanged)
i.e change the UIControlEvents to "EditingChanged"

Resources