Using UserDefaults to save a high score in swift - swift

I"m trying to get a high score to save each time the player exits the app, but I'm struggling to get it to work and I'm confused as to where I'm making a mistake.
var gameSettings = Settings.sharedInstance
let HIGHSCORE = "HIGHSCORE"
if gameSettings.highScore < score{
gameSettings.highScore = score
UserDefaults.standard.set(gameSettings.highScore, forKey: HIGHSCORE)
}
if gameSettings.highScore >= score{
gameSettings.score = score
}
let gameOverHighScore = SKLabelNode()
gameOverHighScore.name = "gameOverHighScore"
gameOverHighScore.fontName = "Helvetica Neue UltraLight"
if let HIGHSCORE = UserDefaults.standard.value(forKey: HIGHSCORE) {
gameOverHighScore.text = "HIGHSCORE : \(HIGHSCORE)M"
}
gameOverHighScore.fontColor = SKColor.white
gameOverHighScore.position = CGPoint(x: 0, y: -175)
gameOverHighScore.fontSize = 70
gameOverHighScore.zPosition = 52
addChild(gameOverHighScore)

In if let HIGHSCORE = UserDefaults.standard.value(forKey: HIGHSCORE) {
gameOverHighScore.text = "HIGHSCORE : \(HIGHSCORE)M"
}
you are try find value in UserDefaults not by key HIGHSCORE, but by new value HIGHSCORE of type ANY. If you want to fix it, you can do like this:
if let HIGHSCORE = UserDefaults.standard.value(forKey: self. self.HIGHSCORE) as! Int {
gameOverHighScore.text = "HIGHSCORE : \(HIGHSCORE)M" }
or you can unwrap value in new value with another name. And don't forget to cast value to appropriate type or use
UserDefaults.standard.integer(forKey: self.HIGHSCORE)

Here is a simple example on how to do it:
let firebaseId = user.uid // Variable we want to save
UserDefaults.standard.set(firebase, forKey: "firebase") // Saving the variable with key "firebase"
And now to retrieve:
if let firebaseId = UserDefaults.standard.string(forKey: "firebase") {
print("Firebase ID: \(firebase)")
}
On your code, you are retrieving to HIGHSCORE, and you should be retrieving to something like gameSettings.highScore (Based on what I can infer from your code)

Related

Need to unwrap Int?

I keep getting this error message: Value of optional type 'Int?' must be unwrapped to a value of type 'Int'
with this code:
let data = document.data()
let uid = data["userid"] as? String ?? ""
let location = data["location"] as? String ?? ""
let currentRating = data["currentRating"] as? Int
let usualRating = data["usualRating"] as? Int
var Submission = RatingSubmission(uid: uid, location: location, currentRating: currentRating, usualRating: usualRating)
what do I need to add to currentRating and usualRating in the Submission variable so that it runs properly?
The reason is that RatingSubmission function works with Int values but you if you assign them as optional values there is a chance to these variables may be "nill". To avoid this you can either remove this option like this:
let currentRating = data["currentRating"] as? Int ?? 0
let usualRating = data["usualRating"] as? Int ?? 0
Second option, if you sure these values are not nill, you can downcasting using "as!".(this is not safe most time)
let currentRating = data["currentRating"] as! Int
let usualRating = data["usualRating"] as! Int
Third option, you can check the values with if let;
let currentRating = data["currentRating"] as? Int
let usualRating = data["usualRating"] as? Int
if let currentRating = Int(data["currentRating"]), let usualRating = Int(data["usualRating"]) {
var Submission = RatingSubmission(uid: uid, location: location, currentRating: currentRating, usualRating: usualRating)
}
I would recommend using the get() method on the document which was made specifically for getting properties from documents.
if let usualRating = document.get("usualRating") as? Int {
print(usualRating)
}
Alternatively, you can one-line it by giving it a default value (of 0 in this case). But this would appear like an anti-pattern to me just on the face of it.
let usualRating = document.get("usualRating") as? Int ?? 0

Save geoFire location under random firebase id swift 4

I'm trying to save the geofire information under each postId but currently not quite sure how to do so. Could someone please help me figure out how to save the information under the postId node.
#objc func handlePost() {
navigationItem.rightBarButtonItem?.isEnabled = false
guard let uid = Auth.auth().currentUser?.uid else { return }
guard let caption = textView.text, caption.characters.count > 0 else { return }
let userPostRef = Database.database().reference().child("posts").child(uid)
let ref = userPostRef.childByAutoId()
guard let locationName = locationNameButton.titleLabel?.text else { return }
let latitude = lat
let longitude = long
let geoLatitude = (latitude as! NSString).doubleValue
let geoLongitude = (longitude as! NSString).doubleValue
geoFireRef = Database.database().reference().child("posts").child(uid)
geoFire = GeoFire(firebaseRef: geoFireRef)
let values = ["caption": caption,"locationName": locationName, "latitude": latitude,"longitude": longitude,"creationDate": Date().timeIntervalSince1970] as [String : Any]
geoFire?.setLocation(CLLocation(latitude: geoLatitude, longitude: geoLongitude), forKey: ref.key!)
If you want to store the geolocation under the same key as
let ref = userPostRef.childByAutoId()
You can simply get the key property from ref. So:
geoFire?.setLocation(CLLocation(latitude: geoLatitude, longitude: geoLongitude), forKey: ref.key)

Stepping through Plist and writing

I created a app for personal use to write data into two Plist files from a template in my BundlePath.
was working great until Swift 3 and i'm unsure about passing the index of the template as the key.
for index in 0 ..< number {
var diction = (resultQDictionary?.object(forKey: "Questions"))
print("diction = \(diction)")
//PROBLEM HERE!! - cannot call value of non function type 'Any?!' for dictionaryQItem and dictionaryAItem
var dictionaryQItem = (resultQDictionary?.object(forKey: "Questions") as AnyObject).object(index)
var dictionaryAItem = (resultADictionary?.object(forKey: "Answers") as AnyObject).object(index)
print("dQitem = \(dictionaryQItem)")
//print("this is DI for question = \(dictionaryItem?.objectForKey("Question"))")
//print(dictionaryItem?.objectForKey("Title"))
var cAns = inputSheetObjectArrayKey[index] //c
var quest = questionArray[index]
var title = titleArray[index]
var block = pickerBlock // blockArray[index]
var answerA = answerAArray[index]
var answerB = answerBArray[index]
var answerC = answerCArray[index]
//print("quest = \(quest)")
let trimmedcAns = (cAns as AnyObject).trimmingCharacters(in: CharacterSet.whitespaces)
let trimmedQuest = quest.trimmingCharacters(in: CharacterSet.whitespaces)
let trimmedTitle = title.trimmingCharacters(in: CharacterSet.whitespaces)
//let trimmedBlock = block.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
let trimmedAnswerA = answerA.trimmingCharacters(in: CharacterSet.whitespaces)
let trimmedAnswerB = answerB.trimmingCharacters(in: CharacterSet.whitespaces)
let trimmedAnswerC = answerC.trimmingCharacters(in: CharacterSet.whitespaces)
dictionaryQItem.setValue(trimmedcAns, forKey: "CorrectAnswer")
dictionaryQItem.setValue(trimmedQuest, forKey: "Question")
dictionaryQItem.setValue(trimmedTitle, forKey: "Title")
dictionaryQItem.setValue(pickerBlock, forKey: "Block")
dictionaryAItem.setValue(trimmedAnswerA, forKey: "A")
dictionaryAItem.setValue(trimmedAnswerB, forKey: "B")
dictionaryAItem.setValue(trimmedAnswerC, forKey: "C")
dictionaryAItem.setValue(trimmedTitle, forKey: "Title")
dictionaryAItem.setValue(" ", forKey: "FEEDBACK")
}
resultQDictionary?.write(toFile: docsQPath, atomically: false)
resultADictionary?.write(toFile: docsAPath, atomically: false)
}
cannot call value of non function type 'Any?!' for dictionaryQItem and dictionaryAItem - was working great in Swift 2.3 but i'm unsure how to pass in the index. Any help appreciated!
I'd define DictionaryAItem and DictionaryQItem as class objects and use them to manage the Questions and Answers data especially as you seem to have well defined structures.

How to write inside a dictionary entry in NSUserDefaults without using temporary variables?

I have this code:
var address = Dictionary<String, AnyObject?>();
address["address1"] = "Here";
address["address2"] = "There";
...
let defaults = NSUserDefaults.standardUserDefaults();
var data = defaults.valueForKey("active_user")! as! Dictionary<String, AnyObject?>
data["address"] = address;
defaults.setValue(data, forKey: "active_user");
defaults.synchronize();
I want to change it into like this:
var address = Dictionary<String, AnyObject?>();
address["address1"] = "Here";
address["address2"] = "There";
...
let defaults = NSUserDefaults.standardUserDefaults();
defaults["active_user"]!["address"]! = address;
defaults.synchronize();
Is this possible? How can I do that?
This is somewhat possible by extending NSUserDefaults to have a subscript. See below:
extension NSUserDefaults {
subscript(key: String) -> Any {
get {
return value(forKey: key)
}
set {
setValue(newValue, forKey: key)
}
}
}
This can then be used like so:
let defaults = NSUserDefaults.standardUserDefaults()
defaults["myKey"] = "someValue"
let myValue = defaults["myKey"]
One limitation is that you won't be able to modify nested collections as you've done in your example. You'll always have to make a manual assignment back to the user defaults object to save something.
// this won't work
defaults["active_user"]!["address"]! = address;
// do this instead
let user = defaults["active_user"]!
user["address"] = address
defaults["active_user"] = user
Edit:
I figured out a way to overload the subscript so you can modify nested collections, with slightly different but very clean syntax. Only works at one level of nesting, but I think it could be taken further. Add this to your NSUserDefaults extension:
subscript(firstKey: String, secondKey: String) -> Any? {
get {
if let dict = value(forKey: firstKey) as? [String: Any] {
return dict[secondKey]
}
return nil
}
set {
if let dict = value(forKey: firstKey) as? [String: Any] {
dict[secondKey] = newValue
setValue(dict, forKey: firstKey)
}
}
}
Then you can do this:
defaults["active_user", "address"] = "some address"
let address = defaults["active_user", "address"]

NSUserDefaults not simply saving high score

Its supposed to be that when my player reaches a new high score, it'll show it on the GameOver scene, but in this case, its not doing that. I have all of my NSUserDefaults in a separate .swift file.
Here is my code:
import Foundation
class GameState {
var score: Int
var highScore: Int
var stars: Int
class var sharedInstance :GameState {
struct Singleton {
static let instance = GameState()
}
return Singleton.instance
}
init() {
// Init
score = 0
highScore = 0
stars = 0
// Load game state
let defaults = NSUserDefaults.standardUserDefaults()
highScore = defaults.integerForKey("highScore")
stars = defaults.integerForKey("stars")
}
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: "highScoreLabel")
defaults.setInteger(stars, forKey: "stars")
NSUserDefaults.standardUserDefaults().synchronize()
}
}
In my GameOver scene, it I have all the text labels set up like so:
lblHighScore.text = String(format: "High Score: %d", GameState.sharedInstance.highScore)
And in my GameScene, I have it so that it should synchronize when the game ends:
func endGame() {
gameOver = true
GameState.sharedInstance.saveState() //right here
print("saved score")
let reveal = SKTransition.fadeWithDuration(0.5)
let gameScene = GameOver(size: self.scene!.size)
view!.presentScene(gameScene, transition: reveal)
print("new scene")
}
Is something missing? Will post more code if necessary.
There is an issue in your coding you may have overlooked:
You are saving as "highScoreLabel"
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setInteger(highScore, forKey: "highScoreLabel")
defaults.setInteger(stars, forKey: "stars")
defaults.synchronize()
But trying to read as "highScore"
highScore = defaults.integerForKey("highScore")
You should use the same key when reading from and writing to NSUserDefaults
For Future Reference...
Personally I set static let variables when dealing with keys. So you never come to this hiccup in the future.
struct Defaults {
static let HighScore = "highScore"
static let Stars = "stars"
}
and then use
highScore = defaults.integerForKey(Defaults.HighScore)
The issue was that none of the variables were being changed, code replaced with:
class GameState {
var highScore: Int
var stars: Int
class var sharedInstance :GameState {
struct Singleton {
static let instance = GameState()
}
return Singleton.instance
}
init() {
// Load game state
highScore = NSUserDefaults.standardUserDefaults().integerForKey(Defaults.HighScore) ?? 0
stars = NSUserDefaults.standardUserDefaults().integerForKey(Defaults.Stars) ?? 0
}
func saveState(score: Int) {
// Update highScore if the current score is greater
highScore = max(score, highScore)
// Store in user defaults
NSUserDefaults.standardUserDefaults().setInteger(highScore, forKey: Defaults.HighScore)
NSUserDefaults.standardUserDefaults().setInteger(stars, forKey: Defaults.Stars)
NSUserDefaults.standardUserDefaults().synchronize()
}
}