NSUserDefault GlobalData/Base Scene Multiple "Keys" Overall Total - swift

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

Related

How to get min/max Values of Realms LinkingObjects

What is the most efficient way to get the overall max speed over all Customers/Cars.
// 157087 Entries (already filtered)
class Customer: Object {
//...
let cars = LinkingObjects(fromType: Car.self, property: "customer")
}
// 2537950 Entries for the 157087 filtered Customers
class Car: Object {
//...
#objc dynamic var customer: Customer?
}
// results here are already filtered (For simplicity i kept the additional filters away)
var results : Results<Customer> = realm.filter("age > 18")
// takes several seconds
let maxSpeed = results.map { $0.cars.max(ofProperty: "speed") as Double? }.max() ?? 0
is there a better way to do so? Just for benchmarking I tried the other way around. It takes just as long
// need to add a `IN` clause because of the pre filtered results (see above)
let cars : Results<Car> = realm.objects(Car.self).filter("customer IN %#", results)
let maxSpeed = cars.max(ofProperty: "speed") as Double?
Super question; clear, concise and property formatted.
I don't have a 2 million row dataset but a few things that may/may not be a complete answer:
1)
This could be bad
results.map { $0.cars.max(ofProperty: "speed") as Double? }.max()
Realm objects are lazily loaded so as long as you work with them in a Realm-ish way, it won't have a significant memory impact. However, as soon as Swift filters, maps, reduce etc. are used ALL of the objects are loaded into memory and could overwhelm the device. Considering the size of your dataset, I would avoid this option.
2)
This is a good strategy as shown in your question as it it treats it as Realm objects, memory won't be affected and is probably your best solution
let max: Double = realm.objects(Car.self).max(ofProperty: "speed") ?? 0.0 //safely handle optional
print(max)
3)
One option we've used in some use cases is ordering the object and then grabbing the last one. Again, it's safer and we generally do this on a background thread to not affect the UI
if let lastOne = realm.objects(Car.self).sorted(byKeyPath: "speed").last {
print(lastOne.speed)
}
4)
One other thought: add a speed_range property to the object and when it's initially written update that as well.
class Car: Object {
#objc dynamic var speed = 0.0
#objc dynamic var speed_range = 0
}
where speed_range is:
0: 0-49
1: 50-99
2: 100-149
3: 150-199
4: 200-249
etc
That would enable you to quickly narrow the results which would dramatically improve the filter speed. Here we want the fastest car in the speed_range of 4 (200-249)
let max: Double = realm.objects(Car.self).filter("speed_range == 4").max(ofProperty: "speed") ?? 0.0
print(max)
You could add a calculated property backed Realm property to the Car object that automatically set's it to the correct speed_range when the speed is initally set.

Accessing A Variable In UserDefaults Swift 3

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.

Swift, SpriteKit, pass variable to other scene

I created a score variable in my GameScene. Now I want to see the result in another scene, for example GameOverScene.
How I can do it ?
You need always try to post some code on stack overflow.
There is loads of way to do what you want.
1) You could use NSUserDefaults, to save the score and access the saved property in another scene and than assigns it to a new score variable.
2) You could make the score property static, so in gameScene you would say
static var score = 0
and than anywhere in your project you can say
GameScene.score = 5
Remember to reset your score to 0 after every game because static properties are having 1 instance only, i.e. they exist for the lifetime of the app.
3) Yet another way is to do a singleton class
class GameData {
static let shared = GameData()
var score = 0
private init() { }
}
Than in your SKscenes you either say
let gameData = GameData.shared
gameData.score = 5
or
GameData.shared.score = 5

Game Scene cannot be constructed because it has not accessible initializers

Xcode is dropping an error and I dont know how to solve it. Maybe someone can give me a hand or a good hint?
Here is my code that is working within my Game Scene:
var currentLevel: Int = 1
var platforms: PlatformsNode!
var platformNode1: PlatformNode!
var platformNode2: PlatformNode!
var platformNode3: PlatformNode!
var platformArray = [SKNode]()
let passenger = SKSpriteNode(imageNamed: "passenger")
var passengerNumber:Int = 1
var startPlatform = [3, 3, 2, 1, 2]
var destinationPlatform = [1, 1, 3, 2, 1]
// Level selection
class func level(levelNum: Int) -> GameScene? {
let scene = GameScene(fileNamed: "Level\(levelNum)")! // <- compiler error
scene.currentLevel = levelNum
scene.scaleMode = .AspectFill
return scene
}
As soon as I want to replace
let passenger = SKSpriteNode(imageNamed: "passenger")
with let passenger: PassengerNode! the compiler drops an compiler error "Game Scene cannot be constructed because it has not accessible initializers".
And this error just shows up, when I change the way I want to declare let passenger.
The reason I want to change it, is that I want to replace it with a class, so that the passenger
a. can have a different type with a different texture (this can may be solved differently)
b. can be removed from the parent and later be added again - with hopefully no error.
You got any ideas? I am really stuck somehow :-?
declaring passenger with only its type and no initial value requires that you add an init() to your class because the compiler does not know what initial value to give it when the object is created.
You can either create an init() and set a value to passenger or delare it as
let passenger: PassengerNode! = nil
(this assumes that your code does not reference the variable before making sure to put something in it or checks for nil/optional before using it).

Making a counter app - Do I have to do something after saving to Core Data?

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.