saving coins variable in swift 3 [duplicate] - ios

This question already has an answer here:
Swift 3.0 save a score with SpriteKit
(1 answer)
Closed 6 years ago.
i am kinda new to swift and i need help with saving an int variable that holds the user's coins that they collect and saving it even after the app closes , so here is what i did , i have two scenes one represents startMenu which has the a struct that has the coins variable ( so i can easily control it from another scene ) , and the other scene which is GameScene , and in GameScene every time the user interacts with a coin node , it adds 1 to the coins variable in StarMenu Scene
here in my struct in StartMenu
struct Variables {
static var CoinsCollected = 0
}
and here is what i did to make it be saved
let defaults = UserDefaults.standard
defaults.set(Variables.CoinsCollected, forKey: "CoinsCollected")
defaults.synchronize()
and i have this line in GameScene inside my didbegin contact , when the user interacts with a coin
Variables.CoinsCollected += 1
and i have this line that updates the labelnode for the coins variable
coinsCollectedLabel = SKLabelNode(text: "coins: \(Variables.CoinsCollected)")
and everything works fine except that it doesn't get saved and i know i am missing something and i tried reading a lot of people's problem with the same issue but it didn't work, so if any of you guys could help me i would be highly appreciate it

You can do this in your Variables class so your coins are always saved in a persistent file:
class Variables {
static let userDefaults = UserDefaults.standard
struct Constants {
static let CoinsCollectedKey = "CoinsCollected"
}
static var CoinsCollected: Int {
get {
return userDefaults.integer(forKey: self.Constants.CoinsCollectedKey)
}
set(value) {
userDefaults.setValue(value, forKey: self.Constants.CoinsCollectedKey)
}
}
}

let defaults = UserDefaults.standard
...
//after Initializing Variables Instance
//get saved value
guard Variables.CoinsCollected = defaults.integer(forKey: "CoinsCollected") else {
print("Previously saved coins don't exist")
//create UserDefaults value for coins
defaults.set(Variables.CoinsCollected, forKey: "CoinsCollected")
}
I think it would be worth saving your entire Variables object instead, so that you can get any new values you add to your struct later - in which case you should serialize your Variables object using (for example)
//set object
defaults.set(Variables, forKey: "Variables")
//read object
let variables: Variables = defaults.object(forKey: "Variables")
//theoretically you should now be able to get CoinsCollected value within your retrieved object~
//e.g.
print(variables.CoinsCollected)

Related

Xcode 8/Swift 3: Reset all variables to initial values

When a gameOver() function is triggered, I would like all variables to reset to their original, unchanged values (as in, the values that are assigned in the ViewController file) when a user presses a Restart button. How can I do this?
Depending on the specific variables you have, one solution would be to create a struct with the fixed initial values and assign those to your active game variables in the Restart function.
You can define a function that reset the variables to the default value like this :
func resetGame(){
score = 0 // or default value
life = 3 // or default value
//.... and so on
}
Assume the restart button are connected to this function
#IBAction func restartGame(sender: UIButton){
gameOver()
resetGame()
}
you can call this function after calling gameOver() inside the restart button function.
If your question is about how to declare default values you can use struct as Jay said like this :
struct DefaultValues {
let score = 0
let lifes = 3
let level = 1
}
and resetGame() will be like this :
func resetGame(){
score = DefaultValues().score
life = DefaultValues().life
level = DefaultValues().devel
}

Load Highscore on Main Menu of game SWIFT XCODE

So I have created a game and I already know how to load and save the high score for the game and I want to know how to display the high score on the main screen (the main menu is the first view to appear) and the high score is saved on the 2nd view which is the game view is there a way to pass the data to the main menu without changing to the second view? Please help, thanks to anyone who replies!
You can use NSUserdefaults for this task. In your second view set your Highscore value into NSUserdefaults like below,
NSUserDefaults.standardUserDefaults().setObject(YourValue, forKey:"HighScore")
NSUserDefaults.standardUserDefaults().synchronize()
And then in your first view's viewdidload method just add this,
if(NSUserDefaults.standardUserDefaults().objectForKey("HighScore") as! String != "") {
self.yourLabelName.text = HigheScore
} else {
print("No highscore set.")
}
Create a class like below class that has at least two methods saveHighScore and readHighScore and in all viewControllers and also all other classed that you want to access the score create an instance of this class and call the methods you want.
Swift
public class ScoreManager: NSObject {
let highScoreDefaultKey = "HighScoreDefaultKey"
public func saveHighScore (highScore: NSNumber) {
//This method includes your implementation for saving the high score
//You can use UserDefault or any other data store like CoreData or
//SQLLit or etc. For example I added UserDefault
NSUserDefaults.standardUserDefaults().setObject(highScore, forKey: highScoreDefaultKey)
NSUserDefaults.standardUserDefaults().synchronize()
}
public func getHighScore() -> NSNumber?
{
//This method includes your implementation for reading the high score
let highScore = NSUserDefaults.standardUserDefaults().objectForKey(highScoreDefaultKey)
if (highScore != nil) {
return highScore as! NSNumber?;
}else
{
//No high score availble, so return nil
return nil;
}
}
}
usage:
var highScore = ScoreManager().getHighScore()
ScoreManager().saveHighScore(NSNumber(int: 120))
Objective-C
#import "ScoreManager.h"
#define USERDEFAULTS_HIGHSCRORE_KEY #"highScore"
#implementation ScoreManager
- (void) saveHighScore:(NSNumber *) highScore
{
//This method includes your implementation for saving the high score
//You can use UserDefault or any other data store like CoreData or
//SQLLit or etc. For example I added UserDefault
[[NSUserDefaults standardUserDefaults] setObject:highScore forKey:USERDEFAULTS_HIGHSCRORE_KEY];
[[NSUserDefaults standardUserDefaults] synchronize];
}
- (NSNumber *) readHighScore
{
//This method includes your implementation for reading the high score
NSNumber *highScore = [[NSUserDefaults standardUserDefaults] objectForKey:USERDEFAULTS_HIGHSCRORE_KEY];
if (highScore) {
return highScore;
}else
{
//No high score availble, so return nil
return nil;
}
}
#end
The advantage are that:
1- The implementation of your read and write process is only in one place and you can change it (for example switch from UserDefaults to CoreData) whenever you wants and do not worry about to change all places that you are working with Score
2- Simply call only one method when you want to access to score or write it.
3- Simply debug it when you see a bug or something like this.
Notice: If you are worry about synchronization it is better to use a singleton class that manages this matter.

iOS: Core Data fails to save when attribute is a double

I thought I'd ask here first before I sent my laptop flying out of the window. I am very new to iOS development and have been toying around with Core Data trying to create an expense manager app. I have a single entity named "Account" which has two non-optional attributes (name and initBalance) as follows:
I have also created an NSManagedObject subclass as follows:
//Account.swift
class Account: NSManagedObject {
override func awakeFromInsert() {
super.awakeFromInsert()
initBalance = NSNumber(double: 0.0)
name = ""
}
}
//Account+CoreDataProperties.swift
extension Account {
#NSManaged var initBalance: NSNumber
#NSManaged var name: String
}
The Account entities are presented in the AccountsViewController (a subclass of UITableViewController conforming to the NSFetchedResultsControllerDelegate protocol) using an NSFetchedResultsController. To add a new Account I use a segue from AccountsViewController to direct me to the AccountInfoViewController (shown below) which has two textFields, one for the name and one for the balance of the account. When the latter is about to disappear a new Account is inserted in the context with the name and balance derived from the textFields. Once the parent controller appears (AccountsViewController) it tries to save the changes and updates the tableView.
Now, if I insert a new Account for which the balance is an integer number life is good; the context is able to save the changes, tableView updates its rows by showing the newly inserted account, and I am happy. When I try to insert an Account for which the balance is a decimal number, the app crashes at the point where the context tries to save the changes. I get no useful error at all as to why it happens. Here is the code for the controller managing the textFields:
class AccountInfoViewController: UIViewController {
#IBOutlet weak var nameField: UITextField!
#IBOutlet weak var balanceField: UITextField!
var store: DataStore! // set by the parent viewController everytime this controller appears
let numberFormatter = MyFormatter()
// insert the new account into the context before the controller disappears
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
store.insertAccount(nameField.text!, balance: numberFormatter.numberFromString(balanceField.text!)!)
}
}
The MyFormatter is this:
func MyFormatter() -> NSNumberFormatter {
let formatter = NSNumberFormatter()
formatter.numberStyle = .DecimalStyle
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
return formatter
}
and the DataStore shown above contains a simple Core Data Stack and is responsible for inserting into the context:
// DataStore.swift
class DataStore {
let coreDataStack = CoreDataStack(modelName: "SmartMoney")
// inserts a new account into the context
func insertAccount(name: String, balance: NSNumber) -> Account {
let account = NSEntityDescription.insertNewObjectForEntityForName("Account",
inManagedObjectContext: coreDataStack.mainQueueContext) as! Account
account.name = name
account.initBalance = balance
return account
}
}
Is there any reason why the context fails to save the changes when the balanceField contains a non-integer number? Thank you for reading and please let me know if you would like me to post any other parts of the code.
I was finally able to figure out what's going on. Changing my attribute name from initBalance to startingBalance makes everything work again. In fact, changing the attribute's name to anything that does not start with new or init works just fine. I got the idea from this post.
It seems that when using ARC, your property's name should not start with the word new. It turns out that initBalance (or newBalance for that matter) produces the same issue. Hope it helps the next poor soul running in a similar problem.

Error on Static Array in Swift Class

I am developing an app using swift. I create a subclass from SCNNode for this:
class Charge: SCNNode {
static var tagCounter = 0 //to give every charge a unique tag
static var charges = [Charge]() //to have a pointer that can access all charges
static var selectedCharge: Charge? //pointer to selected charge
override init() {
super.init()
super.geometry = Charge.carbonAtom()
Charge.tagCounter++
self.chargeTag = Charge.tagCounter
Charge.charges.append(self)
}
}
Then after initiating the class several times in ViewController, I want to access them by using Charge.charges. But for some unknown reason, only the last instance is available, and it occupies Charge.charges[0].
I tried to track the object movement by adding property in ViewController var test = [Charge]() and call test.append(charge) every time I initiate charge in ViewController. Then, when I want to access all the charges, Charge.charges loses most of its charges but test does not! Can anyone enlighten me on this? Or is it a bug in Xcode?
n.b. I use debugging tool to track this problem. It turns out that Charge.charges loses its first content as soon as the second initialization is finished, but the first content still exists right after the execution of Charge.charges.append(self)
edit: carbonAtom function
class func carbonAtom() -> SCNGeometry {
let carbonAtom = SCNSphere(radius: 0.8)
carbonAtom.firstMaterial!.diffuse.contents = UIColor.redColor()
carbonAtom.firstMaterial!.specular.contents = UIColor.whiteColor()
return carbonAtom
}
I have just tested, there is not any Xcode bug.
class Charge: NSObject {
static var tagCounter = 0 //to give every charge a unique tag
static var charges = [Charge]() //to have a pointer that can access all charges
override init() {
super.init()
// super.geometry = Charge.carbonAtom()
Charge.tagCounter++
// self.chargeTag = Charge.tagCounter
Charge.charges.append(self)
}
}
Create 3 Changes instance:
for var i = 0; i < 3; i++ {
_ = Charge()
}
print(Charge.charges.count)
The console prints 3.
Try to check your Charge.carbonAtom() static method. I doubt it clear value of the charges array.
OKAY GUYS I FOUND IT!
There are infos that I dont provide (I thought it was irrelevant) in Charge class:
override func removeFromParentNode() {
super.removeFromParentNode()
for var i = 0; i<Charge.charges.count; i++ {
let charge = Charge.charges[i]
if Charge.selectedCharge != nil {
if charge == Charge.selectedCharge! {
Charge.charges.removeAtIndex(i)
}
}
break
}
}
and in ViewController
#IBAction func addCharge(sender: AnyObject) {
let charge = Charge()
scene.rootNode.addChildNode(charge) //root of problem
Charge.selectedCharge = charge
print(Charge.charges.count)
}
what happen is, in the line scene.rootNode.addChildNode(charge), the method automatically calls removeFromParentNode() from charge. Because of that, the charge pointed by Charge.selectedCharge will be removed from Charge.charges when the second charge is initialized.
I try to search for this info in Apple documentation but no avail. If only they document it more comprehensively :/
Thank you for the help guys :) the divide and conquer method I use to replicate the bug really helps narrowing down the problem

fatal error: unexpectedly found nil while unwrapping an Optional value - UITableView Add Button

I am trying to add names into a UITableView. The names will be stored as a NSUserDefault.
My viewDidLoad:
var PlayersUserDefault = NSUserDefaults.standardUserDefaults()
if (PlayersUserDefault.arrayForKey("playersKey") != nil){
players = PlayersUserDefault.arrayForKey("playersKey")
}
Add name button:
#IBAction func addButtonAction(sender: AnyObject) {
players!.append(namesTextBox.text)
myTableView.reloadData()
}
When I press the addButtonAction, I am getting error on following:
players!.append(namesTextBox.text)
Players declaration:
var players = NSUserDefaults().arrayForKey("playersKey")
Any idea why I am getting error?
Error image:
In one line you use NSUserDefaults.standardUserDefaults() and in the other you create new instance of NSUserDefaults and ask for "playersKey". What you should do is to use NSUserDefaults.standardUserDefaults() for retrieving AND saving your players.
Take a look at the refence:
https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/#//apple_ref/doc/constant_group/NSUserDefaults_Domains
For init:
This method does not put anything in the search list. Invoke it only
if you’ve allocated your own NSUserDefaults instance instead of using
the shared one.
and for shared instance:
If the shared defaults object does not exist yet, it is created with a
search list containing the names of the following domains, in this
order:
NSArgumentDomain, consisting of defaults parsed from the application’s
arguments
A domain identified by the application’s bundle identifier
NSGlobalDomain, consisting of defaults meant to be seen by all
applications
Separate domains for each of the user’s preferred languages
NSRegistrationDomain, a set of temporary defaults whose values can be
set by the application to ensure that searches will always be
successful
The defaults are initialized for the current user. Subsequent
modifications to the standard search list remain in effect even when
this method is invoked again—the search list is guaranteed to be
standard only the first time this method is invoked.
The players array is nil at the point of time thats why the array is not unwrapped. If you tap the button at the first time, your UserDefaults will not be having players, So the players array is nil. So you can solve this in two ways do like this.
On your viewDidLoad()
var PlayersUserDefault = NSUserDefaults.standardUserDefaults()
if (PlayersUserDefault.arrayForKey("playersKey") != nil){
players = PlayersUserDefault.arrayForKey("playersKey")
}else {
players = [String]()
}
Otherwise, initialise the players array with an empty string array like this:
var players = [String]()
This may help you.
You could try unwrapping the value of players before attempting to do append. Then you will know for sure if it's nil or not.
Try:
#IBAction func addButtonAction(sender: AnyObject) {
if let playerValue = players {
players!.append(namesTextBox.text)
myTableView.reloadData()
}else{
println("players is nil!")
}
}

Resources