Saving basic data in Swift - iOS - ios

I have a simple layout when the app is ran, just allowing the user to input some text, and if the text is right, the score counter will save the data received, and not run the for loop again
#IBAction func checkAnswer(sender: AnyObject) {
while canAdd {
if output.text == "hi" {
canAdd = false
rightOrWrong.text = "Right!"
scoreTotal += 1
count.text = String(scoreTotal)
} else {
canAdd = true
rightOrWrong.text = "Try again!"
}
}
}
What's the easiest way to do this?
Thanks

As your code stands now, if the user were to enter a wrong number, your loop would be performed over and over again an infinite number of times since canAdd would always equal true.
I think you're misunderstanding the purpose of a while loop... A while loop will continue to perform over and over and over again without pausing or waiting for user input as long a certain condition is met, so your current while loop can run forever thousands of times per second thus probably preventing user interaction and potentially crashing your program.
If you only want to score to increment/not increment once each time the button is pressed, you don't need to use the while loop. Just add your canAdd condition to your current if conditional. I think this is what you're trying to do:
#IBAction func checkAnswer(sender: AnyObject) {
if output.text == "hi" && canAdd == true {
canAdd = false
rightOrWrong.text = "Right!"
scoreTotal += 1
count.text = String(scoreTotal)
} else {
canAdd = true
rightOrWrong.text = "Try again!"
}
}

Related

How to check array for contents based off another var, utilize alert window through if statement?

I am currently working on a small Bingo app. I'm having a few issues with this, however.
First off, I have button that when clicked, change images showing that that piece is marked (intended to mark the numbers that have been called). I have an if statement as follows:
if BOneButton.isSelected && IOneButton.isSelected && NOneButton.isSelected && GOneButton.isSelected && OOneButton.isSelected {
winConditionLabel.text = "You win!"
func createAlert(_ sender: UIButton) {
let alert = UIAlertController(title: "Bingo!", message: "You win!", preferredStyle: .alert)
let action1 = UIAlertAction(title: "Play Again?", style: .default) { (action) in
self.NewBoardAction(sender)
self.dismiss(animated: true, completion: nil)
}
alert.addAction(action1)
present(alert, animated: true, completion: nil)
}
}
I am having a few issues with this. First off, the Alert window will not come up. Have I done something incorrectly? (This is my first time attempting to use Alerts, so that's very likely.) Secondly, the label, which I have been using to test to make sure that the program gets through the if statement in the first place, only updates after deselecting the final button in the row. This is the main issue I'm trying to fix right now. And lastly, I'm having trouble checking the calledNumbers array (which consists of exactly that - all the numbers that have been called) to make sure that the numbers that have been selected have been called.
I originally had something like this:
if BOneButton.isSelected && IOneButton.isSelected && NOneButton.isSelected && GOneButton.isSelected && OOneButton.isSelected && calledNumbers.contains(Int(randomBOne.text)) && calledNumbers.contains(Int(randomIOne.text)) && calledNumbers.contains(Int(randomNOne.text)) && calledNumbers.contains(Int(randomGOne.text)) && calledNumbers.contains(Int(randomOOne.text)) {
// do stuff here
}
But this wouldn't work at all. Is there a better way to do this? I'd really appreciate any help at all!
Pertaining to the issue with checking the array. Here is the code for the first letter. The others do essentially the same thing:
#IBAction func newNumberAction(_ sender: Any) {
// B NUMBERS
let randomLetter = ["B", "I","N","G","O"]
let randomIndex = Int(arc4random_uniform(UInt32(randomLetter.count))) // Gives random number from 0 - 4
if (randomLetter[randomIndex]) == (randomLetter[0]) // if statement for each index/letter possibility {
let randomBIndex = Int(arc4random_uniform(UInt32(listBNumbers.count))) // listBNumbers is the original array that randomizes a number from 1 - 15
if randomBIndex < 1 // makes sure there are still B numbers remaining and if not runs the newNumberAction again {
newNumberAction((Any).self)
} else {
let newBNumber = "\(listBNumbers[randomBIndex])" // creates unique variable for random BNumbers
let combinedNumber = (randomLetter[0]) + newBNumber
var calledBNumbers = [Int]() // creates array for called BNumbers
calledBNumbers.append(Int(newBNumber)!) // adds called B Number into new array
listBNumbers.remove(at: (Int(randomBIndex))) // removes B Number from bank of possible numbers that could be called, this should ensure that no number can be called twice
calledNumbers += calledBNumbers // adds called B Numbers to new array that will be used later to verify BINGO
newNumLabel.text = combinedNumber
// this randomizes the number and combines it with 'B' then displays it in the label's text. This is used to display the next called Number.
}
I know the append is working by checking a label that gives me the count of the variables in the calledNumbers array. Again, I apologize that I didn't provide enough information originally.
Your first problem the alert won't appear because you have the function declared inside of the if statement. Create it outside of the if statement then call it inside the if statement
Your second problem doesn't include enough context in the code for anyone to help you with. How would anyone other then yourself know what the "final button in the row" is?
Your last problem has the same issue as the second problem, "I'm having trouble checking the calledNumbers array". There isn't an array in your question so how can anyone help you with identifying the problem?
This should fix your First problem:
if BOneButton.isSelected && IOneButton.isSelected && NOneButton.isSelected && GOneButton.isSelected && OOneButton.isSelected {
winConditionLabel.text = "You win!"
createAlert() // now that this inside of the if statment it will run
}
// this should be declared outside of the if statement
func createAlert() {
let alert = UIAlertController(title: "Bingo!", message: "You win!", preferredStyle: .alert)
// your alert's code
}
You added array information but there still isn't enough context. This is the best I could do based on what I ASSUME you wanted. Your code still isn't clear enough. You should've added all the arrays with foo info. For now I added an array var listBNumbers = ["X", "Y", "Z"] with X,Y,Z as the listBNumbers values to use in your code since you didn't provide anything.
var listBNumbers = ["X", "Y", "Z"]
let randomLetter = ["B", "I","N","G","O"]
/*
// YOUR ORIGINAL CODE
let randomIndex = Int(arc4random_uniform(UInt32(randomLetter.count)))
*/
// 1. TEMPORARILY set this to 0 so that if statement below will run. Delete this and uncomment out your original code above when finished
let randomIndex = 0
if randomLetter[randomIndex] == randomLetter[0] {
/*
// YOUR ORIGINAL CODE
// randomIndex is of type INT there is no need to cast it as Int again in step 7B when your removing it from the listBNumbers array
let randomBIndex = Int(arc4random_uniform(UInt32(listBNumbers.count)))
*/
// 2. TEMPORARILY set this to 2 so that the else statement below will run. Delete this and uncomment out your original code above when finished
let randomBIndex = 2
if randomBIndex < 1 {
// I have no idea what this is
newNumberAction((Any).self)
} else {
// 3. newBNumber is now: Z
let newBNumber = "\(listBNumbers[randomBIndex])"
print("newBNumber: \(newBNumber)")
// 4. combinedNumber is now: BZ
let combinedNumber = randomLetter[0] + newBNumber
print("combinedNumber: \(combinedNumber)")
// 5. THIS IS AN ARRAY of type INT it will not accept Strings
var calledBNumbers = [Int]()
// 6A. newBNumber is of type STRING not a type INT. This line below CRASHES because what your saying is calledBNumbers.append(Int(Z)!). How can you cast a Z to an Int?
calledBNumbers.append(Int(newBNumber)!) // newBNumber contains the letter Z
// 6B. THIS IS MY ASSUMPTION seems like you wan to use
calledBNumbers.append(randomBIndex)
print("calledBNumbers: \(calledBNumbers.description)")
// 7A. check to see if the index your looking for is inside the listBNumbers array
if listBNumbers.indices.contains(randomBIndex){
// 7B. randomBIndex is ALREADY of type Int. Why are you casting it >>>Int(randomBIndex)
listBNumbers.remove(at: randomBIndex)
print("listBNumbers: \(listBNumbers.description)")
}
// 8. There is no context for me to even try and figure out what calledNumbers is so I can't give you any info about it
calledNumbers += calledBNumbers
// combinedNumber contains: BZ from the step 4 so that is what your newNumLabel.text will show
newNumLabel.text = combinedNumber
}
}
Here's a way to clean up this code so it's more readable. You need to break all of that down into smaller functions so its more readable:
if areButtonsSelected() == true && doesCalledNumbersContainBingo() == true{
// do stuff here
}
func areButtonsSelected() -> Bool {
if BOneButton.isSelected && IOneButton.isSelected && NOneButton.isSelected && GOneButton.isSelected && OOneButton.isSelected{
return true
}
return false
}
func doesCalledNumbersContainBingo() -> Bool {
let b = Int(randomBOne.text)
let i = Int(randomIOne.text)
let n = Int(randomNOne.text)
let g = Int(randomGOne.text)
let o = Int(randomOOne.text)
if calledNumbers.contains(b) && calledNumbers.contains(i) && calledNumbers.contains(n) && calledNumbers.contains(g) && calledNumbers.contains(o){
return true
}
return false
}

Missing argument for parameter #1 in call to Swift function

Very new to Swift and coding in general.
Just need some help getting my random selector to work. I am trying to make it choose a random number between 1 and 9 and check if that number is the same as the button you click.
enter image description here
I'm guessing you're trying to compare the random number to the title (displayed number) on the clicked button?
If so, add this line above your if statement:
guard let buttonInt = sender.titleLabel?.text.flatMap(Int.init) else { return }
Then, change your if statement to look like so:
if randomNumber == buttonInt {
// do what you want if it matches
} else {
// handle no match
}
// EDIT: Props to Alexander for the flatMap version
Please check :
let rand = Int(arc4random_uniform(9))
guard let buttonInt = Int(sender.currentTitle!) else { return }
if rand == buttonInt {
} else {
}

How to observe a change in a class's property from another class

I've got a question on property observers. There's some example code below. What I want is for the property Analysis.hasChanged to be updated to true if a.value is changed. Is there a way I can do this?
class Number {
var value: Double
init(numberValue: Double) {
self.value = NumberValue
}
}
class Analysis {
var a: Number
var hasChanged = false
init(inputNumber: Number) {
self.a = inputNumber
}
}
testNumber = Number(numberValue: 4)
testAnalysis = Analysis(inputNumber: testNumber)
print(testAnalysis.hasChanged) // will print "false"
testNumber.value = 10
print(testAnalysis.hasChanged) // will still print "false", but I want it to print "true"
In the end, I want the user to be able to be notified if any of their analyses use numbers that have been changed so that they can update the results of the analyses if they choose.
You can use the built-in property observers provided by Swift.
Every time you set a new value, the didSet will be called. You just need to attach the closure, wrapping the desired behaviour, to the Number class
class Number {
var valueDidChangeClosure: (()->())?
var value: Double {
didSet {
//won't call the valueDidChangeClosure
//if the value was changed from 10 to 10 for example..
if oldValue != value {
valueDidChangeClosure?()
}
}
}
init(numberValue: Double) {
self.value = numberValue
}
}
class Analysis {
var a: Number
var hasChanged = false
init(inputNumber: Number) {
self.a = inputNumber
self.a.valueDidChangeClosure = {
self.hasChanged = true
}
}
}
let testNumber = Number(numberValue: 4)
let testAnalysis = Analysis(inputNumber: testNumber)
print(testAnalysis.hasChanged) // will print "false"
testNumber.value = 10
print(testAnalysis.hasChanged) // will print "true"
I would do something like this, I apologize in advance if I have some syntax wrong (I usually use C/C++, think of this as more psudo code since you'd have to have a way to copy Number classes, etc.).
class Number {
var value: Double
init(numberValue: Double) {
self.value = NumberValue
}
}
class Analysis {
var a: Number
var _a: Number
bool hasChanged() {
if (a != _a) {
_a = a
return true;
}
return false;
}
init(inputNumber: Number) {
self.a = inputNumber
self._a = self.a
}
}
testNumber = Number(numberValue: 4)
testAnalysis = Analysis(inputNumber: testNumber)
print(testAnalysis.hasChanged()) // will print "false"
testNumber.value = 10
print(testAnalysis.hasChanged()) // will still print "false", but I want it to print "true"
In the end, I want the user to be able to be notified if any of their analyses use numbers that have been changed so that they can update the results of the analyses if they choose.
I don't know if this really addresses that question, I based my answer off of the code you provided. So there may be additional functionality if you want there to be some triggering method (instead of calling .hasChanged()).
Comparing doubles (and any other floating point type) with '=' or '!=' is not a good idea.
Use epsilon function instead.
Details: jessesquires.com/blog/floating-point-swift-ulp-and-epsilon/

Why is this dispatch_after firing instantly?

Here's a struct I've written to convert an NSTimeInterval into a walltime-based dispatch_time_t:
public struct WallTimeKeeper {
public static func walltimeFrom(spec: timespec)->dispatch_time_t {
var mutableSpec = spec
let wallTime = dispatch_walltime(&mutableSpec, 0)
return wallTime
}
public static func timeStructFrom(interval: NSTimeInterval)->timespec {
let nowWholeSecsFloor = floor(interval)
let nowNanosOnly = interval - nowWholeSecsFloor
let nowNanosFloor = floor(nowNanosOnly * Double(NSEC_PER_SEC))
println("walltimekeeper: DEBUG: nowNanosFloor: \(nowNanosFloor)")
var thisStruct = timespec(tv_sec: Int(nowWholeSecsFloor),
tv_nsec: Int(nowNanosFloor))
return thisStruct
}
}
I've been trying to test the accuracy of it in a Playground, but my results are confusing me.
Here's the code in my Playground (with my WallTimeKeeper in the Sources folder):
var stop = false
var callbackInterval: NSTimeInterval?
var intendedTime: NSDate?
var intendedAction: ()->() = {}
func testDispatchingIn(thisManySeconds: NSTimeInterval){
intendedTime = NSDate(timeIntervalSinceNow: thisManySeconds)
intendedAction = stopAndGetDate
dispatchActionAtDate()
loopUntilAfterIntendedTime()
let success = trueIfActionFiredPunctually() //always returns false
}
func dispatchActionAtDate(){
let timeToAct = dateAsDispatch(intendedTime!)
let now = dateAsDispatch(NSDate())
/*****************
NOTE: if you run this code in a Playground, comparing the above two
values will show that WallTimeKeeper is returning times the
correct number of seconds apart.
******************/
dispatch_after(timeToAct, dispatch_get_main_queue(), intendedAction)
}
func loopUntilAfterIntendedTime() {
let afterIntendedTime = intendedTime!.dateByAddingTimeInterval(1)
while stop == false && intendedTime?.timeIntervalSinceNow > 0 {
NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode,
beforeDate: afterIntendedTime)
}
}
func trueIfActionFiredPunctually()->Bool{
let intendedInterval = intendedTime?.timeIntervalSinceReferenceDate
let difference = intendedInterval! - callbackInterval!
let trueIfHappenedWithinOneSecondOfIntendedTime = abs(difference) < 1
return trueIfHappenedWithinOneSecondOfIntendedTime
}
func dateAsDispatch(date: NSDate)->dispatch_time_t{
let intendedAsInterval = date.timeIntervalSinceReferenceDate
let intendedAsStruct = WallTimeKeeper.timeStructFrom(intendedAsInterval)
let intendedAsDispatch = WallTimeKeeper.walltimeFrom(intendedAsStruct)
return intendedAsDispatch
}
func stopAndGetDate() {
callbackInterval = NSDate().timeIntervalSinceReferenceDate
stop = true
}
testDispatchingIn(3)
...so not only doestrueIfActionFiredPunctually() always returns false, but the difference value--intended to measure the difference between the time the callback fired and the time it was supposed to fire--which in a successful result should be really close to 0, and certainly under 1--instead comes out to be almost exactly the same as the amount of time the callback was supposed to wait to fire.
In summary: an amount of time to wait is defined, and an action is set to fire after that amount of time. When the action fires, it creates a timestamp of the moment it fired. When the timestamp is compared to the value it should be, instead of getting close to zero, we get close to the amount of time we were supposed to wait.
In other words, it appears as if the action passed to dispatch_after is firing immediately, which it absolutely shouldn't!
Is this something wrong with Playgrounds or wrong with my code?
EDIT:
It's the code. Running the same code inside a live app gives the same result. What am I doing wrong?
I figured it out. It's a head-smacker. I'll leave it up in case anyone is having the same problem.
I was using NSDate().timeIntervalSinceReferenceDate to set my walltimes.
Walltimes require NSDate().timeIntervalSince1970!
The dispatch_after tasks all fired instantly because they thought they were scheduled for over forty years ago!
Changing everything to NSDate().timeIntervalSince1970 makes it work perfectly.
Moral: don't use walltimes unless you're sure your reference date is 1970!

The count in my For loop is not incrementing

When running my code, I am getting a number of 1's printing to the console rather than 1,2,3,4,5....
Some help with why this is happening would be great, I'm having trouble figuring it out.
The idea is to loop through the Calendar names until finding the 'Travel' calendar.
func checkCalendarExists(){
var eventCalendars = store.calendarsForEntityType(EKEntityTypeEvent) as [EKCalendar]
for i in eventCalendars {
var count = 0
var calendarCount = eventCalendars.count
if i.title != "Travel" && count != calendarCount
{
++count
println(count)
}
else if i.title == "Travel"
{
// do something
}
else
{
aMethod()
}
}
}
Your count variable is not being incremented because it is declared inside the loop and initialized to the value zero at the beginning of each iteration. For your code to work as expected you have to move var count = 0 outside the for loop.
Your count variable does get incremented, but it resets to zero every time the for loop runs its sequence.
It's always advised to declare and assign incrementing variables outside loops.
Please change your code to (I am initializing var count = 0 before the loop)
func checkCalendarExists(){
var eventCalendars = store.calendarsForEntityType(EKEntityTypeEvent) as [EKCalendar]
var count = 0
for i in eventCalendars {
var calendarCount = eventCalendars.count
......
......
......
else
{
aMethod()
}
}
}
ALXGTV's answer explains why you have that unexpected behavior.
Your code can be optimized though - rather than manually handling a counter variable, I recommend using the enumerate function, which returns a (index, value) at each iteration:
for (index, calendar) in enumerate(eventCalendars) {
...
}
Also this variable:
var calendarCount = eventCalendars.count
is populated at each iteration, always with the same value. It would be more efficient if it is moved before the loop, making it immutable:
let calendarCount = eventCalendars.count
for (index, calendar) in enumerate(eventCalendars) {
...
}
Last, I would prefer using a flag for the not found condition, handling it outside the loop:
func checkCalendarExists() {
var eventCalendars = store.calendarsForEntityType(EKEntityTypeEvent) as [EKCalendar]
var found = false
let calendarCount = eventCalendars.count
for (index, calendar) in enumerate(eventCalendars) {
if calendar.title == "Travel" {
// do something
found = true
break // This stops the loop
} else {
println(index + 1)
}
}
if !found {
aMethod()
}
}

Resources