I have a game with three different game modes and a high score screen each in a different view controller. I'm storing the high scores using NSUserDefaults but the values will change throughout the runtime of the application.
The application will save the high scores correctly and when I press clear the scores will all set to 0 and it will show on the screen. If I leave the app and come back the scores will still be zero, but if I replay one of the game modes and return to the high scores screen all the scores will reset to what they where before I cleared them.
Ex: arcade score == 4, easy score == 7, opposite score == 21
I clear the scores and they all are set and show to be zero. Then I play arcade mode and get a score of 6 and return to the high score screen. The values should be (arcade == 6, easy == 0, opposite == 0), but they show to be arcade == 6, easy == 7, opposite == 21.
In one of the GameViewControllers:
func lose(){
// save score
let defaults = NSUserDefaults.standardUserDefaults()
var arcade_score = defaults.integerForKey("arcade")
if(arcade_score < cur+1){ // cur == current level
defaults.setInteger(cur+1, forKey: "arcade")
defaults.synchronize()
}
}
This method is called whenever the player loses the game. This method is similar in each game controller but stores the value in a different key.
In my HighScoreViewController I load and show the values in the viewDidLoad() method and have a button to clear the values:
class HighScoreViewController: UIViewController {
#IBOutlet weak var arcade: UILabel!
#IBOutlet weak var easy: UILabel!
#IBOutlet weak var opposite: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let defaults = NSUserDefaults.standardUserDefaults()
var arcade_score = defaults.integerForKey("arcade")
var easy_score = defaults.integerForKey("easy")
var opposite_score = defaults.integerForKey("opposite")
arcade.text = String(arcade_score)
arcade.sizeToFit()
easy.text = String(easy_score)
easy.sizeToFit()
opposite.text = String(opposite_score)
opposite.sizeToFit()
}
#IBAction func clearScores(sender: UIButton) {
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setInteger(0, forKey: "arcade")
defaults.setInteger(0, forKey: "easy")
defaults.setInteger(0, forKey: "opposite")
defaults.synchronize()
arcade.text = String(defaults.integerForKey("arcade"))
arcade.sizeToFit()
easy.text = String(defaults.integerForKey("easy"))
easy.sizeToFit()
opposite.text = String(defaults.integerForKey("opposite"))
opposite.sizeToFit()
}
}
What is wrong with my logic?
Related
I am programming an iOS app using swift where i have 3 UILabels which will show data different sensors data into the same corresponding labels.
These are 3 labels which i am using.
#IBOutlet weak var xAccel: UILabel!
#IBOutlet weak var yAccel: UILabel!
#IBOutlet weak var zAccel: UILabel!
I am using UIsegmentedControl to change the display of data which is as follows.
#IBAction func AccelDidChange(_ sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case 0:
myAccelerometer()
break
case 1:
myGyroscope()
break
default:
myAccelerometer()
}
Above used 2 functions are as follows
func myAccelerometer() {
// sets the time of each update
motion.accelerometerUpdateInterval = 0.1
//accessing the data from the accelerometer
motion.startAccelerometerUpdates(to: OperationQueue.current!) { (data, error) in
// can print the data on the console for testing purpose
//print(data as Any)
if let trueData = data {
self.view.reloadInputViews()
//setting different coordiantes to respective variables
let x = trueData.acceleration.x
let y = trueData.acceleration.y
let z = trueData.acceleration.z
//setting the variable values to label on UI
self.SensorName.text = "Accelerometer Data"
self.xAccel.text = "x : \(x)"
self.yAccel.text = "y : \(y)"
self.zAccel.text = "z : \(z)"
}
}
}
func myGyroscope() {
motion.gyroUpdateInterval = 0.1
motion.startGyroUpdates(to: OperationQueue.current!) { (data, error) in
if let trueData = data {
self.view.reloadInputViews()
//setting different coordiantes to respective variables
let x = trueData.rotationRate.x
let y = trueData.rotationRate.y
let z = trueData.rotationRate.z
//setting the variable values to label on UI
self.SensorName.text = "Gyroscope Data"
self.xAccel.text = "x: \(x)"
self.yAccel.text = "y: \(y)"
self.zAccel.text = "z: \(z)"
}
}
}
**
Problem is it keeps on displaying both the Accelerometer and Gyroscope data on UILabels at the same time instead of only showing the data of a particular sensor when tapped. I have tried to use the break option but still not working. If any one could point out the possible solution, that would be great. Thanks
**
EIDT -
Here is the output on screen where you can see the values fluctuates between different sensors. I only want readings from 1 sensor at a time.
https://imgur.com/a/21xW4au
#IBAction func AccelDidChange(_ sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case 0:
motion.stopGyroUpdates()
myAccelerometer()
break
case 1:
motion.stopDeviceMotionUpdates()
myGyroscope()
break
default:
myAccelerometer()
}
}
You need to stop unneeded resource before the switch. Try this please
I get the following error when I run the app and try and click any key on the keypad that opens on launch.
Here is the error I get in the attached photo
Below is my code and it highlights the first func at the bottom called func formatAsCurrancy... as the problem area. I have converted all the old code in the book as it prompted me to. Not sure what to do at this point. Thank you for any help.
import UIKit
class ViewController: UIViewController {
//properties for programmatically interacting with UI components
#IBOutlet weak var billAmountLabel: UILabel!
#IBOutlet weak var customTipPercentLabel1: UILabel!
#IBOutlet weak var customTipPercentageSlider: UISlider!
#IBOutlet weak var customTipPercentLabel2: UILabel!
#IBOutlet weak var tip15Label: UILabel!
#IBOutlet weak var total15Label: UILabel!
#IBOutlet weak var tipCustomLabel: UILabel!
#IBOutlet weak var totalCustomLabel: UILabel!
#IBOutlet weak var inputTextField: UITextField!
// NSDecimalNumber constants used in the calculateTip method
let decimal100 = NSDecimalNumber(string: "100.0")
let decimal15Percent = NSDecimalNumber(string: "0.15")
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// select inputTextField so keypad displays when the view loads
inputTextField.becomeFirstResponder()
}
#IBAction func calculateTip(_ sender: Any) {
let inputString = inputTextField.text //get user input
// convert slider value to an NSDecimalNumber
let sliderValue =
NSDecimalNumber(integerLiteral: Int(customTipPercentageSlider.value))
// divide sliderValue by decimal100 (100.0) to get tip %
let customPercent = sliderValue / decimal100
// did customTipPercentageSlider generate the event?
if sender is UISlider {
// thumb moved so update the Labels with new custom percent
customTipPercentLabel1.text =
NumberFormatter.localizedString(from: customPercent,
number: NumberFormatter.Style.percent)
customTipPercentLabel2.text = customTipPercentLabel1.text
}
// if there is a bill amount, calculate tips and totals
if !(inputString?.isEmpty)! {
// convert to NSDecimalNumber and insert decimal point
let billAmount =
NSDecimalNumber(string: inputString) / decimal100
// did inputTextField generate the event?
if sender is UITextField {
billAmountLabel.text = " " + formatAsCurrency(number: billAmount)
// calculate and display the 15% tip and total
let fifteenTip = billAmount * decimal15Percent
tip15Label.text = formatAsCurrency(number: fifteenTip)
total15Label.text =
formatAsCurrency(number: billAmount + fifteenTip)
}
// calculate custom tip and display custom tip and total
let customTip = billAmount * customPercent
tipCustomLabel.text = formatAsCurrency(number: customTip)
totalCustomLabel.text =
formatAsCurrency(number: billAmount + customTip)
}
else {// clear all Labels
billAmountLabel.text = ""
tip15Label.text = ""
total15Label.text = ""
tipCustomLabel.text = ""
totalCustomLabel.text = ""
}
}
}
// convert a numeric value to localized currency string
func formatAsCurrency(number: NSNumber) -> String {
return NumberFormatter.localizedString(
from: number, number: NumberFormatter.Style.currency)
}
// overloaded + operator to add NSDecimalNumbers
func +(left: NSDecimalNumber, right: NSDecimalNumber) -> NSDecimalNumber {
return left.adding(right)
}
// overloaded * operator to multiply NSDecimalNumbers
func *(left: NSDecimalNumber, right: NSDecimalNumber) -> NSDecimalNumber {
return left.multiplying(by: right)
}
// overloaded / operator to divide NSDecimalNumbers
func /(left: NSDecimalNumber, right: NSDecimalNumber) -> NSDecimalNumber {
return left.dividing(by: right)
}
The simulator is trying to find a numeric keyboard on your computer, but your computer shouldn't have one so that's why it's giving you the error.
If you go to the simulator and then Hardware -> Keyboard -> Uncheck use hardware keyboard, the issue will go away.
I would like the label foxmill to store a high score using a user default. Obviously if there is a new high score I would like it to be replaced. The problem is that totalTime (what tracks the score) is a double and not a int. All of the tutorials use a int for the high score tutorial.
import UIKit
class winViewController: UIViewController {
#IBOutlet var score2: UILabel!
#IBOutlet var winningLabel: UILabel!
#IBOutlet var foxMill: UILabel!
#IBOutlet var hhighscore: UILabel!
public var LebelText: String?
public var LebelText2: String?
public var LebelText3: String?
public var LebelText4: String?
override func viewDidLoad() {
super.viewDidLoad()
timeCalculation()
loadState()
}
func saveScore(score: Double) {
// Instantiate user defaults
let userDefaults:UserDefaults = UserDefaults.standard
// Set your score
userDefaults.set(score, forKey: "highScore")
// Sync user defaults
userDefaults.synchronize()
}
func loadState() {
let userDefaults = UserDefaults.standard
let score = userDefaults.double(forKey: "highScore")
foxMill.text = "High Score: \(score)"
}
func timeCalculation(){
guard let unwrapedText = self.LebelText2 else {
return
}
guard let unwrapedText2 = self.LebelText else {
return
}
guard let unwrapedText3 = self.LebelText3 else {
return
}
guard let unwrapedText4 = self.LebelText4 else {
return
}
if let myInt = Double(unwrapedText), let myInt2 = Double(unwrapedText2), let myInt3 = Double(unwrapedText3), let myInt4 = Double(unwrapedText4)
{
var totalTime = myInt + myInt2 + myInt3 + myInt4
self.winningLabel.text = "You won"+"\n"+"Reaction time :" + String(totalTime) + " Seconds"
saveScore(score: totalTime)
}}}
You just save the value with the proper key, then when you retrieve it, make sure to do so as a Double:
func saveScore(score: Double) {
// Instantiate user defaults
let userDefaults:UserDefaults = UserDefaults.standard
// Set your score
userDefaults.set(score, forKey: "highScore")
// Sync user defaults
userDefaults.synchronize()
}
Now when you update your score values, when you say ... (unwrappedText), to make sure you save the user defaults
if let myInt = Double(unwrapedText), let myInt2 = Double(unwrapedText2), let myInt3 = Double(unwrapedText3), let myInt4 = Double(unwrapedText4)
// HERE you do whatever it was you originally did with the text, but also save a value
saveScore(score: myInt)
{
Now the next time you load your app, to load the score:
func loadScore() {
// Instantiate User Defaults
let userDefaults = UserDefaults.standard
let score = userDefaults.double(forKey: "highScore")
}
You really shouldn't name a property "myInt" when you want it to be a Double and not an Integer
If you want your foxMill label to display this high score at load, you want to do two things. First make sure before you app closes at some point you save your state by synchronizing your UserDefaults, look into methods like applicationDidEnterBackground and applicationWillTerminate in your AppDelegate - but that's a little beyond the scope of your question. For now I'll assume you've properly saved your state last time the app quit by calling the saveScore(score:) function I listed above. So now you want to retrieve the score and update your foxMill label at load. You would do something like this:
You could do something like this, by amending the loadScore method above:
func loadScore() {
// Instantiate User Defaults
let userDefaults = UserDefaults.standard
let score = userDefaults.double(forKey: "highScore")
foxMill.text = "High Score: \(score)"
}
And then at the end of viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
loadScore()
}
Or you could change the method to return a string which you would then assign to your label's text:
func loadScore() -> String {
// Instantiate User Defaults
let userDefaults = UserDefaults.standard
let score = userDefaults.double(forKey: "highScore")
return "High Score: \(score)"
}
Then in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
foxMill.text = loadScore()
}
I´m trying to display the cadence and pace data of CMPedometer. When I run the application with my phone attached, it writes the output of the data immediately through the print("...") function into the console, but takes multiple turns until it displays the data in the UILabel.
How can I get the data as fast as possible so I can use them?
Best, Zack
import UIKit
import CoreMotion
class ViewController: UIViewController {
let pedometer = CMPedometer()
#IBOutlet weak var paceLabel: UILabel!
#IBOutlet weak var cadenceLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
guard CMPedometer.isCadenceAvailable() && CMPedometer.isPaceAvailable() else{
print("Pace and cadence data are not available")
return
}
let oneWeekAgo = NSDate(timeIntervalSinceNow: -(7 * 24 * 60 * 60))
pedometer.startUpdates(from: oneWeekAgo as Date) {data, error in
guard let pData = data , error == nil else{
return
}
//The current pace of the user, measured in seconds per meter. (1 step = 83cm?)
if let pace = pData.currentPace{
print("Pace = \(pace)")
self.paceLabel.text = "Pace = \(round(Double(pace))*10/10)"
}
//The rate at which steps are taken, measured in steps per second.
if let cadence = pData.currentCadence{
self.cadenceLabel.text = "Cadence = \(cadence))"
print("Cadence = \(cadence)")
}
}// -----------------oneWeekAgo
}// -----------------ViewDidLoad
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}//-------------------- UIViewController
The update block is called on a background thread and you need to update your UI on the main thread. Wrap the UI update calls in a dispatch back to the main thread:
Dispatch.main.async {
//The current pace of the user, measured in seconds per meter. (1 step = 83cm?)
if let pace = pData.currentPace{
print("Pace = \(pace)")
self.paceLabel.text = "Pace = \(round(Double(pace))*10/10)"
}
//The rate at which steps are taken, measured in steps per second.
if let cadence = pData.currentCadence{
self.cadenceLabel.text = "Cadence = \(cadence))"
print("Cadence = \(cadence)")
}
}
}
When I try to load my high score and see if the score I just got was bigger, I am getting some problems. First it seems to be adding some of the previous score, making the high score larger. Also, if the score I get is a negative number larger than the high score, it becomes the new high score only positive. Here is my high score view code:
import UIKit
class HighScore: ViewController {
#IBOutlet weak var HighscoreLabel: UILabel!
#IBOutlet weak var Scorelabel: UILabel!
#IBOutlet weak var NewHighscoreLabel: UILabel!
override func viewDidLoad() {
let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
var score = defaults.valueForKey("Score") as? String
Score = score?.stringByTrimmingCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet).toInt() ?? 0
Scorelabel.text = score
let SecondDefaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
var highscore = SecondDefaults.valueForKey("Highscore") as? String
Highscore = highscore?.stringByTrimmingCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet).toInt() ?? 0
HighscoreLabel.text = highscore
if Score < 0 {
NewHighscoreLabel.text = ""
}
else if Score > Highscore {
Highscore = 0
Highscore += Score
var HighscoreString:String = String(format: "Highscore:%i", Highscore)
let SecondDefaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
SecondDefaults.setObject(HighscoreString, forKey: "Highscore")
SecondDefaults.synchronize()
HighscoreLabel.text = highscore
NewHighscoreLabel.text = "New Highscore!"
}
}
}
Here is my saving code for the score on the actual game page:
var ScoreString:String = String(format: "Score:%i", Score)
ScoreLabel.text = (string: ScoreString)
let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(ScoreString, forKey: "Score")
defaults.synchronize()
First, some problems in your code: there is no need to create a SecondDefaults variable. It is always the same singleton referring to the defaults data base, so you can continue using your first variable defaults.
Variables, by convention, start with lower case letters. The use of your uppercase letters ivars is very confusing because they look like class names.
The logic is not quite clear because you are retrieving values from user defaults in viewDidLoad and then comparing them and re-setting some values. That does not seem logical. The user defaults entries should be in order when you read them - you should only adjust them when you evaluate a new score.
There are various other illogical lines, e.g.
highscore = 0
highscore += score
is exactly equivalent to
highscore = score
Back to your problem: you should not save your high score as a string. Not surprisingly, you run into all sorts of problems doing that because you have to encode and decode these strings. Instead, store your scores as numbers - you can just use them without having to parse strings.
The error you get is presumably because you have no value in one of the defaults entries. You should initialize your variables upon app start to make sure they are there. The best way to do this is the registerDefaults API.
let seedData = ["Score" : 0, "Highscore" : 0]
defaults.registerDefaults(seedData)
This will initialize them to 0 only if these keys are not already there.
The rest is easy:
let score = defaults.integerForKey("Score")!
scoreLabel.text = "\(score)"
let highscore = defaults.integerForKey("Highscore")!
highscore.text = "\(highscore)"
newHighscoreLabel.text = ""
Note that you can force unwrap with ! because you know the variables are initialized. As pointed out above, reading the score from user defaults does not make any sense. More likely you are displaying a score screen and get a new score value. Thus
defaults.setInteger(score, forKey: "Score")
let highscore = defaults.integerForKey("Highscore")!
if score > highscore {
defaults.setInteger(score, forKey: "Highscore")
newHighscoreLabel.text = "New Highscore!"
}