UISegmentedControl and .selectedSegmentIndex wrong values? - ios

I have a simple Segment in my code with 3 elements. For testing purposes I also do have a variable that increments based on which of the segments I press (3). The value of that variable is printed in a UITextView. This is the code:
import UIKit
class ViewController: UIViewController
{
#IBOutlet weak var segment: UISegmentedControl!
#IBOutlet weak var prwtoView: UIView!
#IBOutlet weak var prwtoText: UITextField!
var i : Int = 0
override func viewWillAppear(animated: Bool)
{
prwtoText.backgroundColor = UIColor.purpleColor()
prwtoText.textColor = UIColor.whiteColor()
segment.setTitle("Zero", forSegmentAtIndex: 0)
segment.addTarget(self, action: "action", forControlEvents: .ValueChanged)
segment.insertSegmentWithTitle("random", atIndex: 2, animated: false)
}
func action()
{
let argumentForSegment = segment.selectedSegmentIndex
if argumentForSegment == 0
{
i = 0
}
if argumentForSegment == 1
{
i += 2
}
else
{
i += Int(arc4random_uniform(100))
}
print(argumentForSegment)
prwtoText.text = "\(i)"
}
}
While I know that it starts with value -1 i don't want my app to do anything if not pressed. The thing is that even when I press the first segment (0) and it is supposed to make i = 0 it doesn't do that, although if I print argumentForSegment in my terminal it does show the 0 as value. Concluding, every time I press the zero segment (0), my i value won't become 0. Perhaps I am using the wrong method from UISegmentedControl?
edit: Got it fixed by changing the following code:
func action()
{
let argumentForSegment = segment.selectedSegmentIndex
if argumentForSegment == 0
{
i = 0
}
if argumentForSegment == 1
{
i += 2
}
else
{
i += Int(arc4random_uniform(100))
}
print(argumentForSegment)
prwtoText.text = "\(i)"
}
to:
func action()
{
let argumentForSegment = segment.selectedSegmentIndex
if argumentForSegment == 0
{
i = 0
}
if argumentForSegment == 1
{
i += 2
}
else if argumentForSegment == 2 // <==== here
{
i += Int(arc4random_uniform(100))
}
print(argumentForSegment)
prwtoText.text = "\(i)"
}
Could someone explain why it used the priority of else although the value was zero when printing argumentForSegment? In other words why when I had an else alone for the value of argumentForSegment == 0 it chose the else instead of the first statement?

Could someone explain why it used the priority of else although the
value was zero when printing argumentForSegment? In other words why
when I had an else alone for the value of argumentForSegment == 0 it
chose the else instead of the first statement?
When you have a situation where the code is not behaving as you expect, it is helpful to step through it in the debugger, or add some diagnostic print statements.
For example:
func action()
{
let argumentForSegment = segment.selectedSegmentIndex
if argumentForSegment == 0
{
print("In first block")
i = 0
}
if argumentForSegment == 1
{
print("In second block")
i += 2
}
else
{
print("In third block")
i += Int(arc4random_uniform(100))
}
print(argumentForSegment)
prwtoText.text = "\(i)"
}
If you do this, you will notice that when argumentForSegment is 0, the output will be:
In first block
In third block
So, the problem is not that it is choosing the third block over the first. The problem is that it is doing both. You want it to stop after it has detected that argumentForSegment is 0, so add an else to the second conditional statement so that it only does that when the first conditional statement failed:
func action()
{
let argumentForSegment = segment.selectedSegmentIndex
if argumentForSegment == 0
{
i = 0
}
else if argumentForSegment == 1 // added "else" here
{
i += 2
}
else
{
i += Int(arc4random_uniform(100))
}
print(argumentForSegment)
prwtoText.text = "\(i)"
}

To improve on Vacawama's answer, you can format this much easier by using a switch statement:
func action() {
let argumentForSegment = segment.selectedSegmentIndex
switch argumentForSegment {
case 0:
i = 0
case 1:
i += 1
case 2:
i += Int(arc4random_uniform(100))
default:
break
}
print(argumentForSegment)
prwtoText.text = "\(i)"
}
it's much more clean for this type of thing.
(thanks, vacawama)

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.

How do I return the next array when a button is pressed?

I'm new to programming and Swift and have been following along with a tutorial. I'm learning about the MVC design pattern and I have a function that changes the Title label and Button labels when a button is pressed. Once we get to Story 1 or Story 2 and we select any of the choices, I want it to restart to Story 0 and the original choices. However, when the story changes to Story 1, choice2 triggers Story 2 and when the story changes to Story 2, choice1 triggers Story1. I want it to reset to Story 0 when any choice is chosen. I tried to modify the nextStory function but have been unsuccessful. Any help would be greatly appreciated.!
Model:
struct StoryBrain {
var storyNumber = 0
let story = [Story(t: "You see a fork in the road.", c1: "Take a left.", c2: "Take a right."),
Story(t: "You see a tiger.", c1: "Shout for help.", c2: "Play dead."),
Story(t: "You find a treasure chest", c1: "Open it.", c2: "Check for traps.")
]
func getStoryTitle() -> String {
return story[storyNumber].title
}
func getChoice1() -> String {
return story[storyNumber].choice1
}
func getChoice2() -> String {
return story[storyNumber].choice2
}
mutating func nextStory(_ userChoice: String) {
if userChoice == story[storyNumber].choice1 {
storyNumber = 1
} else if userChoice == story[storyNumber].choice2 {
storyNumber = 2
}
}
}
View Controller
#IBAction func choiceMade(_ sender: UIButton) {
storyBrain.nextStory(sender.currentTitle!)
updateUI()
}
func updateUI() {
storyLabel.text = storyBrain.getStoryTitle()
choice1Button.setTitle(storyBrain.getChoice1(), for: .normal)
choice2Button.setTitle(storyBrain.getChoice2(), for: .normal)
}
If storyNumber is 0 then check choice for update next story.
If storyNumber is not 0 it might be 1 or 2 you should reset story to 0.
mutating func nextStory(_ userChoice: String) {
if storyNumber == 0 {
if userChoice == story[storyNumber].c1 {
storyNumber = 1
} else if userChoice == story[storyNumber].c2 {
storyNumber = 2
}
} else {
storyNumber = 0
}
}
I hope this help.
Hopefully, I am understanding you correctly. You just want to restart once you reach story 2.
mutating func nextStory(_ userChoice: String) {
guard storyNumber != 0 else { storyNumber = 0; return }
if userChoice == story[storyNumber].choice1 {
storyNumber = 1
return
} else if userChoice == story[storyNumber].choice2 {
storyNumber = 2
return
}
}
Hopefully, something like this works for you. Just off the top of my head.

Error when add negative numbers in calculator

I have this code:
class MainViewController: UIViewController {
#IBOutlet weak var summaryLbl: UILabel!
var actualNumber: Double = 0
var previousNumber: Double = 0
var operationMath: Bool = false
var operation = 0
#IBAction func numberPressed(_ sender: UIButton) {
if operationMath == true {
summaryLbl.text = String(sender.tag)
actualNumber = Double(summaryLbl.text!)!
operationMath = false
} else {
if summaryLbl.text == "0" {
summaryLbl.text = ""
}
summaryLbl.text = summaryLbl.text! + String(sender.tag)
actualNumber = Double(summaryLbl.text!)!
}
}
#IBAction func buttons(_ sender: UIButton) {
if summaryLbl.text != "" && sender.tag != 10 && sender.tag != 17 {
previousNumber = Double(summaryLbl.text!)!
if sender.tag == 13 {
summaryLbl.text = "/"
} else if sender.tag == 14 {
summaryLbl.text = "x"
} else if sender.tag == 15 {
summaryLbl.text = "-"
} else if sender.tag == 16 {
summaryLbl.text = "+"
} else if sender.tag == 11 {
var number: Double = Double(summaryLbl.text!)!
number.negate()
let rounded = number.rounded()
summaryLbl.text = String(rounded).replacingOccurrences(of: ".0", with: "", options: .literal, range: nil)
}
operation = sender.tag
operationMath = true
} else if sender.tag == 17 {
var result: Double = 0
var rounded: Double = 0
if operation == 13 {
result = previousNumber / actualNumber
} else if operation == 14 {
result = previousNumber * actualNumber
} else if operation == 15 {
result = previousNumber - actualNumber
} else if operation == 16 {
result = previousNumber + actualNumber
} else if operation == 12 {
result = previousNumber.truncatingRemainder(dividingBy: actualNumber)
}
rounded = result.rounded()
if (result == rounded) {
summaryLbl.text = String(result).replacingOccurrences(of: ".0", with: "", options: .literal, range: nil)
} else {
summaryLbl.text = String(result)
}
} else if sender.tag == 10 {
summaryLbl.text = "0"
previousNumber = 0
actualNumber = 0
operation = 0
}
}
override func viewDidLoad() {
super.viewDidLoad()
summaryLbl.text = "0"
previousNumber = 0
actualNumber = 0
operation = 0
}
}
This is simple calculator.
I have a problem with calculations.
When I click the buttons, for example: 2 + 5 * -
then the application turns off with an error. When I enter such a key combination: 2 + 5 =
This calculation will be done correctly.
 
How do I add commas to numbers?
Does anyone know how to fix the above problems?
A calculator is a Finite State Machine. It can be very complex but in its simplest form it resembles this:
So if we keep things simple and take the above machine as our target, after 2 + 5, our machine expects equals(=) to calculate the result or if an operator is added (like * in our case) it will expect a digit next. giving an operator (minus in our case) will result in an error.
The complexity is limited only by your imagination. You can add support for decimal point numbers, brackets, powers etc. The more sugar you want to add the more complex the FSM will become.
I suggest starting with the simplest one. Maintain your states, the transitions allowed next and error handling in case of wrong transition.
Check this repo on github for Finite State Machine in swift: https://github.com/vishalvshekkar/SwiftFSM
And the corresponding article:
https://blog.vishalvshekkar.com/finite-state-machine-in-swift-ba0958bca34f

Array Index Out Of Range - Error when optional unbinding

I have an entity called Settings with an attribute called backgroundColor of type Int, if it is 1 then the view controller will have a background of white if 0 then a background of dark grey.
But I am getting the following error when trying to open the view controller;
fatal error: Array Index out of range
For the following line in my function
if settingsArray.count == 1 {
setting = settingsArray[1]
} else if settingsArray.count <= 0 {
println("No settings in array")
}
View Controller
var settingsArray: [Settings]!
var setting: Settings!
var backgroundSetting: Bool = true
override func viewWillAppear(animated: Bool) {
backgroundSettings()
}
override func viewDidLoad() {
super.viewDidLoad()
backgroundSettings()
}
// Function to fetch settings and change background
func backgroundSettings() {
var error: NSError?
let request = NSFetchRequest(entityName: "Settings")
self.settingsArray = moc?.executeFetchRequest(request, error: &error) as! [Settings]
if settingsArray.count == 1 {
setting = settingsArray[1]
} else if settingsArray.count <= 0 {
println("No settings in array")
}
if setting != nil {
if setting.backgroundColor == 1 {
backgroundSetting = true
} else if setting.backgroundColor == 0{
backgroundSetting = false
}
}
if backgroundSetting == true {
self.view.backgroundColor = UIColor.whiteColor()
} else if backgroundSetting == false {
self.view.backgroundColor = UIColor.darkGrayColor()
}
}
//Button to change the color and settings
#IBAction func backgroundColor(sender: AnyObject) {
if setting != nil {
if setting.backgroundColor == 1 {
setting.backgroundColor = 0
} else {
setting.backgroundColor = 1
}
var error: NSError?
moc?.save(&error)
} else {
println("No settings available")
var settings = NSEntityDescription.insertNewObjectForEntityForName("Settings", inManagedObjectContext: moc!) as! Settings
settings.backgroundColor = 1
var error: NSError?
moc?.save(&error)
}
backgroundSettings()
}
Any ideas where I may be going wrong ?
In Swift (as in Objective C and many other languages), the indexes of arrays start at 0. (See this wikipedia article for a list on this.)
This means when you check if settingsArray.count == 1, there will be only (exactly) one item in your list. Since indexes start at 0, this item will be at index 0, hence the Error.
So either you check if settingsArray.count == 2 and leave setting = settingsArray[1], or you change to setting = settingsArray[0].
ETA:
I have been having another think about this. I have left my old answer below so that the previous comments make sense.
In if let thisSetting = settingsArray?[0] … if settingsArray is nil then the right side is potentially effectively nil[0]. Therefore I believe that this may eliminate the crash:
// ...
if !settingsArray.isEmpty {
if let thisSetting = settingsArray?[0] {
setting = thisSetting
}
}
settingsArray[0] being nil would I think then be a separate issue. Which I think would relate to the lifecycle.
Previous answer follows:
I believe that the problem may be being caused by you calling func backgroundSettings() from viewDidLoad() - i.e. too early in the View Controller lifecycle - and before the values have been initialized.

Prime number checker returns the same result each time

I'm a beginner programmer learning Swift and made a basic prime number checker. No matter what it will only give one result, instead of changing based on wether or not the number is prime. Any help would be appreciated.
#IBAction func primeCheck(sender: AnyObject) {
var numberInt = number.text.toInt()
var isPrime = true
if number != nil {
if numberInt == 1 {
isPrime = false
}
if numberInt != 1 {
for var i = 2; i < numberInt; i++ {
if numberInt! % i == 0 {
isPrime = false
} else {
isPrime = true
}
}
}
}
if isPrime == true {
result.text = "\(numberInt!) is a prime number!"
} else {
result.text = "\(numberInt!) is not a prime number!"
}
}
I have another possible solution. At first I divide by two because it cannot be a prime number. Then you loop until the number is prime or the number divided by two is less than the divider.
#IBAction func primeCheck(sender: AnyObject) {
var numberInt = number.text.toInt()
var isPrime = true
var divider = 3
if number < 2 || (number != 2 && number % 2 == 0) {
isPrime = false
}
// you only have to check to half of the number
while(isPrime == true && divider < number / 2){
isPrime = number % divider != 0
divider += 2
}
if isPrime == true {
result.text = "\(numberInt!) is a prime number!"
} else {
result.text = "\(numberInt!) is not a prime number!"
}
}
The error in your logic comes in this section:
if numberInt! % i == 0 {
isPrime = false
} else {
isPrime = true
}
At the top of your function, you initialize isPrime to be true, so in your loop you only need to look for cases that prove the number is not prime. You don't ever need to set isPrime = true again, so just drop the else condition:
if numberInt! % i == 0 {
isPrime = false
}
You actually have two functions here. One to check if a number is prime and the other to display the result. Separating these makes everything much easier to manage.
// function to check primality and return a bool
// note that this can only accept a non optional Int so there is
// no need to check whether it is valid etc...
func checkNumberIsPrime(number: Int) -> Bool {
// get rid of trivial examples to improve the speed later
if number == 2 || number == 3 {
return true
}
if number <= 1 || number%2 == 0 {
return false
}
// square root and round up to the nearest int
let squareRoot: Int = Int(ceil(sqrtf(Float(number))))
// no need to check anything above sqrt of number
// any factor above the square root will have a cofactor
// below the square root.
// don't need to check even numbers because we already checked for 2
// half the numbers checked = twice as fast :-D
for i in stride(from: 3, to: squareRoot, by: 2) {
if number % i == 0 {
return false
}
}
return true
}
// function on the button. Run the check and display results.
#IBAction func primeCheck(sender: AnyObject) {
let numberInt? = numberTextField.text.toInt() // don't call a text field "number", it's just confusing.
if let actualNumber = numberInt {
if checkNumberIsPrime(actualNumber) {
resultLabel.text = "\(actualNumber) is a prime number!" // don't call a label "result" call it "resultLabel". Don't confuse things.
} else {
resultLabel.text = "\(actualNumber) is not a prime number!"
}
} else {
resultLabel.text = "'\(numberTextField.text)' is not a number!"
}
}
It makes it all much easy to read and maintain.
You have to break out of the loop after you find that the number is divisble by another number. Also for prime check you only have to check the divisibility till the square root of the number.
You can also use optional binding to extract numberInt and check for nil. That's the swift way.
#IBAction func primeCheck(sender: AnyObject) {
var isPrime = true
if let numberInt = number.text.toInt() {
if numberInt == 1 {
isPrime = false /
}
else // Add else because you dont have to execute code below if number is 1
{
if numberInt != 1 {
for var i = 2; i * i <= numberInt; i++ { // Only check till squareroot
if numberInt % i == 0 {
isPrime = false
break // Break out of loop if number is divisible.
} // Don't need else condition because isPrime is initialised as true.
}
}
}
if isPrime {
result.text = "\(numberInt) is a prime number!"
} else {
result.text = "\(numberInt) is not a prime number!"
}
}
}
Reason for square root check : Why do we check up to the square root of a prime number to determine if it is prime?
You can refine the code further by refactoring the prime check into a separate function.
func isPrime(number:Int) -> Bool
{
if number == 1 {
return false
}
else
{
if number != 1 {
for var i = 2; i * i <= numberInt; i++ {
if numberInt % i == 0 {
return false
}
}
}
}
return true
}
#IBAction func primeCheck(sender: AnyObject) {
if let numberInt = number.text.toInt() {
if isPrime(numberInt) {
result.text = "\(numberInt) is a prime number!"
} else {
result.text = "\(numberInt) is not a prime number!"
}
}
}
Well i don't know about swift, but maybe this is wrecking your code:
if numberInt! <<
To do a faster algorithm you could just search for divisors from 2 to sqrt(numberInt). (Theorem)

Resources