This is my code for setting the defaults.
override func viewDidLoad() {
super.viewDidLoad()
let defaults = UserDefaults.standard
defaults.set("\(coins) $", forKey: "labelName") }
coins is my integer variable. It increases every time someone clicks a button.
labelName is my label that shows how many coins are earned.
How to make it so the number of coins are saved locally and then updated when someone restarts the app?
I wouldn't save coins as a string which is what you're doing here:
defaults.set("\(coins) $", forKey: "labelName")
Instead save it as an Integer:
// Set data (whenever you change the value)
var coins = 100
UserDefaults.standard.set(coins, forKey: "Money")
When you want to get the data back (in viewDidLoad perhaps):
// Get Data
coins = UserDefaults.standard.integer(forKey: "Money")
I think you can save coins as an integer instead of a string, but then if you are using it as a string then you would have that change it back to a string after you accessed the data when you want to use it. If that makes sense.
Related
Totally stumped on why the below code isn't saving correctly. I have gone through and printed each variable individually to make sure they all have data and all the way to latestBottle everything is fine. But when I try to pull the value back from UserDefaults, it's 0.0.
func saveBottleEntry() {
let allBottleEntries = realm.objects(SubmittedEntry.self).filter("bottleQuantity > 0")
for bottle in allBottleEntries {
bottleTimes.append(bottle.submissionTime!)
}
latestBottle = bottleTimes.max()!
UserDefaults.standard.set(latestBottle, forKey: "latestBottle")
print(UserDefaults.standard.double(forKey: "latestBottle"))
}
bottleTimes is an array of dates being retrieved from a Realm database. I then take the newest date bottleTimes.max() and save that to UserDefaults.
If you see 0.0 when try to get double that means that you wrote wrong type to UserDefaults
This may seem like a basic question but I don't know the answer. In my app, you earn coins after every play of the game. Here is my code for earning coins and saving them
let defaultCoins = UserDefaults()
var coinNumber = defaultCoins.integer(forKey: "coinSaved")
coinNumber = coinNumber + score
defaultCoins.set(coinNumber, forKey: "coinSaved")
I would like to be able to access coinNumber from a different view controller and be able to use it. So maybe make a variable = coinNumber that I can access anywhere. I have a shop and would like to be able to purchase a new character when the user has 1000 coins but currently cant do that as I don't know how to access coinNumber from my shopViewController
Since you're saving the data to UserDefaults it doesn't seem like you should need to propagate it between view controllers - just use the defaults as your 'source of truth' for the correct value. Instead of saving it to a custom UserDefaults object, however, just save it to the standard one that is created for all iOS/OS X apps: UserDefaults.standard
Just read it from it whenever you need it the way you're already doing above. A couple things to note about this approach:
You can use Key/Value observing to detect changes to the value when other controllers modify it, but you'll need to be careful as KVO can be easy to crash if you forget to remove observers properly.
You should probably call synchronize on it which forces the changes you've made to disk. This will make sure that if your app is closed suddenly that you've secured the data.
UserDefaults is great for when you have less than 10 pieces of data you're wanting to save, like a highest score or a set of preferences (that's what it's made to do), but it's not great for large data structures. You should look into CoreData or KeyedArchiver for bigger stuff.
Pro-tip: Rather than using the loose string "coinSaved" over and over again, define a static constant somewhere, or use an enumeration of a string type to pre-define all your values once. Then you get code completion and avoid errors.
So, your code above would look more like:
In one view controller:
let defaultCoins = UserDefaults.standard
var coinNumber = defaultCoins.integer(forKey: "coinSaved")
coinNumber = coinNumber + score
defaultCoins.set(coinNumber, forKey: "coinSaved")
defaultCoins.synchronize()
Then, in another controller where you need to read it:
let defaultCoins = UserDefaults.standard
let coinNumber = defaultCoins.integer(forKey: "coinSaved")
Your main problem is that you are creating a new UserDefaults object whenever you try to access it, you should either use the UserDefaults.standard instance or create one with a specific identifier.
I would also suggest creating a singleton to manage the coins if you are going to use them in several view controllers, this is to save your time writing the same safety checks over and over (like making sure you can't spend more coins than you have)
It would look something like this:
class CoinManager {
static let shared = CoinManager()
private var _coins: UInt {
didSet {
UserDefaults.standard.set(_coins, forKey: "coinSaved")
}
}
var coins: UInt { return _coins }
private init() {
_coins = UInt(UserDefaults.standard.integer(forKey: "coinSaved"))
}
func spend(coins: UInt) -> Bool {
guard _coins > coins else { return false }
_coins -= coins
return true
}
func store(coins: UInt) -> UInt {
_coins += coins
return _coins
}
}
As you can see, other objects can only read the coins property but they can't modify it directly, if they want to modify it they have to use either spend or store. spend makes sure you can't spend more coins than you have.
I have multiple scenes that are stored in Global/BaseScene
Each SceneType: is stored as enum: set to an Integer similar to this present scene
First objective was to get the score to populate in each scene, there is seven of them. DONE thanks to stack overflow, and experimentation
I had to create default keys for each individual scene, to calculate the highScore, so I have seven unique "highScore" "keys" for each scene.
In the GameScene:
var highScoreA1: Int = 0
var score: Int = 0 {
didSet {
a1Score.text = "Score: \(score)"
}
}
//above called before override func didMoveToView(view: SKView) {
//Called in GameOver Method
let scoreDefault = NSUserDefaults.standardUserDefaults()
scoreDefault.setInteger(score, forKey: "score")
if (score > highScoreA1){
let highScoreA1Default = NSUserDefaults.standardUserDefaults()
highScoreA1Default.setInteger(score, forKey: "highScoreA1")
//highscoreA1Default.synchronize()
There are six more keys similar to this.. My objective is to populate a "totalScoreKey" in two different scenes a Hud Scene and another scene (possibly game over scene)
I was thinking a function to add these keys together to populate the total score.
Taking into consideration all these scenes are subclasses (of Global BaseScene, and each scene has sub classes (for the nodes operation, probably not relevant yet thought it might be useful)
I have tried: Moving all score data into a Class and using NSCoding/NSObject the required init, and optional binding, became a serious pain, and honestly I am trying to keep things simple for version one.
import Foundation
class GameState {
var score:Int = 0
var highScore:Int()
var totalScore:Int()
class var sharedInstance: GameState {
struct Singleton {
static let instance = GameState()
}
return Singleton.instance
}
}
init() {
// Init
score.type = 0
highScore = 0
totalScore = 0
// Load game state
let defaults = NSUserDefaults.standardUserDefaults()
highScore = defaults.integerForKey("highScore")
}
func saveState() {
// Update highScore if the current score is greater
highScore = max(score, highScore)
// Store in user defaults
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setInteger(highScore, forKey: "highScore")
defaults.setInteger(totalScore, forKey: "totalScore")
NSUserDefaults.standardUserDefaults().synchronize()
}
Various functions that have not worked they all default to zero, that is until I figure out how to retrieve data properly.
let total score = "totalScoreKey"
similar to this post exactly like this post actually except I had to do different configurations because of my on personal set up..
total Score example I tried to implement
outside of class and referring to that in the scene I needed to populate that data. NO Go defaults to zero.
How do I simply add the value of those keys together? For which I can display similar to the other scenes, I already have implemented.
later on down the road I may want to assign a key chain value, right now I am just trying to get it show up for posting in GameCenter. (which also has key "GameCenterHighScore")
Setting them all to the same key "highScore" does not work.... just to be clear, I tried multiple times. Thanks in advance.
EDIT
if I try to add all the defaults together to get the total, it throws the following error:
Expression was too complex to be solved in reasonable time; consider
breaking up the expression into distinct sub-expressions
[Swift compound arithmetic operation ERROR3
Trying my hand in Swift and creating my first "app". I'm essentially creating a cookie-clicker clone where you just click an image in the center of your screen repeatedly to increment your score.
I'm experiencing some issues where clicking the image once will increment my score by 1, but upon clicking it again, the score stays 1.
I'm almost positive I'm supposed to do something after saving to CoreData - I just don't know what. Do I have to refresh the View? Sorry for the newbie question
#IBAction func tapHomeImage(sender: AnyObject) {
//Score is the name of my Entity, with "points" being an attribute
let entity = NSEntityDescription.entityForName("Score", inManagedObjectContext: managedObjectContext!)
let score = Score(entity: entity!, insertIntoManagedObjectContext: managedObjectContext!)
// **As an aside question, is there a more efficient way to code the below? The issue I have is that score.points is of type NSNumber
let intScore = score.points as Int
let incrementedIntScore = intScore + 1
score.points = NSNumber(integer: incrementedIntScore)
var error: NSError?
managedObjectContext?.save(&error)
//homeScoreLabel is an IBOutlet for the Score displayed on the page
homeScoreLabel?.text = "\(score.points)"
}
Thank you very much!
You don't need to do anything after saving, you need to do something before calling this method. You need to find an instance of Score that already exists, and use that.
When you get to this line:
let score = Score(entity: entity!, insertIntoManagedObjectContext: managedObjectContext!)
You create a new instance of the Score entity, every time this method runs. So when you do this:
let intScore = score.points as Int
let incrementedIntScore = intScore + 1
score.points = NSNumber(integer: incrementedIntScore)
You're working with a brand new instance every time. The score is initially zero, and you always increase it to 1.
What you should do:
Use a single instance of Score and save that as a property of your view controller. That is, have something like this:
var score: Score?
Assign a value to this property once, when the view controller loads. Since you're saving data, you should use executeFetchRequest to look up a previously-saved instance. If no previous instance exists, then create a new one via insertIntoManagedObjectContext.
Use that instance in this IBAction.
There are other ways to fix the code. The key is to make sure that you use the same instance of Score every time instead of creating a new one at every tap.
I'm creating a simple game. if the player loses i want his current score to be compared with the best score saved as persistent storage as NSUserDefault. i keep on getting "UInt8 is not convertible to Int8" this is the code
scoreForComparingWithBestScore = UInt8(score)
if ( scoreForComparingWithBestScore > NSUserDefaults.standardUserDefaults().objectForKey("BestScore")){
NSUserDefaults.standardUserDefaults().objectForKey("BestScore") = scoreForComparingWithBestScore
}
even if i typed
scoreForComparingWithBestScore = Int8(score)
the error will be switched.
Thanks in advance !
NSUserDefaults.standardUserDefaults().objectForKey("BestScore”) returns an AnyObject?. To compare this with a UInt8 (or any other Swift numeric value type – you might want to reconsider UInt8 and use Int instead unless there’s a very good reason to stick with that type), you need to both unwrap the optional and also convert it to a number:
if let best = NSUserDefaults.standardUserDefaults().objectForKey("BestScore")?.integerValue {
// best will be an Int here, you can compare it to scoreForComparingWithBestScore
}
else {
// either there was no BestScore value, or there was but it wasn't an integer
}
Alternatively, you could use the ?? operator to default the best score to zero if not present (or not an integer):
let best = NSUserDefaults
.standardUserDefaults()
.objectForKey("BestScore")?
.integerValue ?? 0
NSSUserDefaults has convenience methods to store different types of objects.
In your case, rather than storing objects directly and then trying to convert them to the correct type, you could just use the convenience methods instead:
if let best = NSUserDefaults.standardUserDefaults().integerForKey("BestScore") {
// best will be an Int here, you can compare it to scoreForComparingWithBestScore
}
else {
// There was no BestScore value.
}
As long as you set the value with:
NSUserDefaults.setInteger(best, forKey:"BestScore")
Then because of the strong type system you will know that you have put an integer into the defaults and you then only need to check if a value was set for that key instead of also worrying about whether the object was of the correct type.
This will also drive your design, as you know that the value should be an Int rather than over optimising it is an Int8 or a UInt8.
I got here some examples for how to save NSUserDefaults and how to retrieve it. What you are seeing here is how I save text from an UITextField and how I save a UISwith. You will also see how I retrieve this information out of my NSUserDefaults.
func saveNSUserDefaults() {
let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(self.textFieldUrl.text + "/portal/silvertabletmenu.aspx", forKey: "url")
defaults.setBool(switchToolbar!.on, forKey: "toolbar");
defaults.synchronize()
}
func loadNSUserDefaults() {
let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
if let urlIsNotNil = defaults.objectForKey("url") as? String {
self.textFieldUrl.text = defaults.objectForKey("url") as String
}
if textFieldUrl.text == "" {
self.textFieldUrl.text = "https://thisisjustanexample.net/"
}
switchToolbar!.on = NSUserDefaults.standardUserDefaults().boolForKey("toolbar")
}