I'm trying to create a leaderboard, but the player names and scores are not permanently saved, the "leaderboard" only contains the data from the most recent game in its text view. I tried making arrayOfData initially hold playerName and finalScore instead of being an empty array, but the problem still remains. How can I display playerName and playerScore in the leaderboard permanently and hove more names and scores added as more people play?
var finalScore = Int()
var playerName = String()
var allMyStoredData = UserDefaults.standard
var arrayOfData: [Any] = []
class secondVC: UIViewController {
#IBOutlet weak var scoreLabel: UILabel!
#IBOutlet weak var nameTF: UITextField!
#IBOutlet weak var doneButton: UIButton!
var playerScore = 0
var arrayOfData: [Any] = []
override func viewDidLoad() {
super.viewDidLoad()
scoreLabel.text = "Your score is: \(finalScore)"
loadData()
}
#IBAction func donePressed(_ sender: Any) {
saveData()
performSegue(withIdentifier: "toLeaderboard", sender: self)
}
func saveData () {
playerName = nameTF.text!
playerScore = finalScore
arrayOfData.append(playerName)
arrayOfData.append(playerScore)
allMyStoredData.set(playerName, forKey: "saveTheName")
allMyStoredData.set(playerScore, forKey: "saveTheScore")
allMyStoredData.set(arrayOfData, forKey: "saveTheArray")
}
func loadData () {
if let loadPlayerName:String = UserDefaults.standard.value(forKey: "saveTheName") as? String {
playerName = loadPlayerName
}
if let loadTheScore:Int = UserDefaults.standard.value(forKey: "saveTheName") as? Int {
playerScore = loadTheScore
}
}
}
//this is the code in the leaderboard's view controller
class leaderboardViewController: UIViewController {
#IBOutlet weak var theTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
theTextView.text = "\((arrayOfData).map{"\($0)"}.joined(separator: " Score: "))"
}
}
You are overwriting your saveTheArray item UserDefaults each time with a new array.
You want to fetch the saved array first, append the new game data, then re-save as so:
//Create user defaults instance
let userDefaults = UserDefaults.standard
//User defaults key
let historicalGameDataKey = "historicalGameData"
//Fetch game data array
var historicalGameData = userDefaults.array(forKey: historicalGameDataKey)
//Create array if it is nil (i.e. very first game)
if historicalGameData == nil {
historicalGameData = []
}
//Create game data dictionary
let gameData = [
"playerName": playerName,
"playerScore": playerScore
]
//Append game data dictionary to the array
historicalGameData?.append(["playerName": gameData])
//Save the updated array
userDefaults.set(historicalGameData, forKey: historicalGameDataKey
Core Data would be a better way to store this data, but this example should solve your problem using UserDefaults.
Related
Have made a simple incremental game. I want it so that even if I go to a different VC and then return to the game, or reopen the game, it will show the current score. My issue is that if I leave the game, then the score will return back to zero. I do not want that to happen. The score should not be resetting. I've heard I could use UserDefaults but I am not familiar to it at all. If you can explain using code, that would be best. Thanks.
import UIKit
class CookieClickerVC: UIViewController {
#IBOutlet weak var goldLabel: UILabel!
#IBOutlet weak var numberOfGold: UILabel!
#IBOutlet weak var getGoldButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
formatItems()
let defaults = UserDefaults.standard
defaults.set(0, forKey: "goldCount")
}
#IBAction func getGoldClicked(_ sender: Any) {
goldCount += 1
numberOfGold.text = ("\(goldCount)")
}`
also, you may have already figured out, but goldCount is an unresolved identifier. How should I change the code after button is clicked?
Step 1: declare your goldCount variable and set the initial value to 0
Step 2: on viewDidLoad, get the value stored in the UserDefaults and set it to goldCount
Step 3: Also update your label
Step 4: Update the value of goldCount in userDefaults
import UIKit
class CookieClickerVC: UIViewController {
#IBOutlet weak var goldLabel: UILabel!
#IBOutlet weak var numberOfGold: UILabel!
#IBOutlet weak var getGoldButton: UIButton!
//Step1: declare your goldCount variable and set initial value to 0
var goldCount = 0
override func viewDidLoad() {
super.viewDidLoad()
formatItems()
//Step2: on viewDidLoad, get the value stored in the UserDefaults and set it to goldCount
goldCount = UserDefaults.standard.integer(forKey: "goldCount")
//Step3: then also update your label
numberOfGold.text = ("\(goldCount)")
}
#IBAction func getGoldClicked(_ sender: Any) {
goldCount += 1
numberOfGold.text = ("\(goldCount)")
//Step4: update the value of goldCount in userDefaults
UserDefaults.standard.set(goldCount, forKey: "goldCount")
}
}
You can use these funtions to get and set score
class CookieClickerVC: UIViewController {
#IBOutlet weak var goldLabel: UILabel!
#IBOutlet weak var numberOfGold: UILabel!
#IBOutlet weak var getGoldButton: UIButton!
lazy var goldCount : Int = 0 {
didSet {
numberOfGold.text = ("\(goldCount)")
}
}
override func viewDidLoad() {
super.viewDidLoad()
formatItems()
goldCount = getScore()
}
#IBAction func getGoldClicked(_ sender: Any) {
goldCount += 1
savescore(goldCount)
}
// Save and Get methods ..
func saveScore(_ score:Int) {
let defaults = UserDefaults.standard
defaults.set(score, forKey: "goldCount")
// defaults.synchronize() its [unnecessary][1]
}
// defaults.synchronize() its unnecessary
func getScore()-> Int {
let defaults = UserDefaults.standard
return defaults.integer(forKey: "goldCount")
}
}
Try the below
class ViewController: UIViewController {
#IBOutlet weak var numberOfGold: UILabel!
#IBOutlet weak var highScore: UILabel!
//declare your count variable globally
var goldCount : Int = 0
let defaults = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
//get the highscore and then set it to label
let userHighScore = getUserScore()
highScore.text = "\(userHighScore)"
}
#IBAction func goldButtonClicked(_ sender: Any) {
goldCount += 1
numberOfGold.text = "\(goldCount)"
}
//function to say game finished and store the score in userdefault
//call this function when the gameHasFinished
func gameFinished() {
saveUserScore(goldCount)
}
func saveUserScore(_ score:Int){
defaults.set(score, forKey: "goldCount")
}
func getUserScore()-> Int {
return defaults.integer(forKey: "goldCount")
}
}
You can change your viewDidLoad function to check the UserDefaults for the "goldCount" key first, and if it has a value, use it. If not just set the score to 0.
override func viewDidLoad() {
super.viewDidLoad()
formatItems()
let defaults = UserDefaults.standard
if let savedScore = defaults.value(forKey: "goldCount") as? Int {
goldCount = savedScore
} else {
defaults.set(0, forKey: "goldCount")
}
}
You can define "goldCount" as var goldCount: Int = 0 below your IBOutlets.
Also as a further optimization, you can extract the "goldCount" string to use as let goldCountKey: String = "goldCount" and use it like defaults.set(0, forKey: goldCountKey). Avoiding hard-coded strings is a good practice in my opinion, but not that crucial in this case.
To save the new value in every click, add UserDefaults.standard.set(goldCount, forKey: goldCountKey) in getGoldClicked. Another idea here would be to add this in your AppDelegate's willTerminate function to make sure you get your value saved when the application is terminated. Though that will require references to goldCount value there, so no rush.
Hope it helps!
import UIKit
class CookieClickerVC: UIViewController {
#IBOutlet weak var goldLabel: UILabel!
#IBOutlet weak var numberOfGold: UILabel!
#IBOutlet weak var getGoldButton: UIButton!
private var goldCount: Int = Int()
override func viewDidLoad() {
super.viewDidLoad()
formatItems()
}
#IBAction func getGoldClicked(_ sender: Any) {
goldCount += 1
numberOfGold.text = ("\(goldCount)")
UserDefaults.standard.set(goldCount, forKey: "goldCount")
UserDefaults.standard.synchronize()
//To get the count again you can use
print(UserDefaults.standard.integer(forKey: "goldCount")
}
Whenever there is an increment you can save the value of goldCount like this in your UserDefault and I have also added how can you fetch the same value from UserDefault.
I am manually entering data into a struct. However the struct is not saving the data. I tried to use userdefaults but it's not working. I want the data to appear on the label benCarson at all times if it's in the struct bad.
ViewController 1
struct bad {
static var mm = [String]()
}
ViewController 2
class ViewController2: UIViewController {
#IBOutlet var benCarson: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
benCarson.text = ViewController.bad.mm.map { " \($0)" }.joined(separator:"\n")
}
}
I think no need for struct just use the UserDefaults and it will work
For Save
let mm = ["adsa", "safds", "twer", "qwer", "dfas"]
let defaults = UserDefaults.standard
defaults.set(mm, forKey: "SavedStringArray")
defaults.synchronize()
For Retrieve
let defaults = UserDefaults.standard
let myarray = defaults.stringArray(forKey: "SavedStringArray") ?? [String]()
benCarson.text = myarray.map { " \($0)" }.joined(separator:"\n")
I am using the segmented control as simple yes or no button. I would like to save the result of the selection after I change views by using NSuserdefaults the segmented control has two selections simply "yes" or "no" I have this currently
class DetailContactViewController: UIViewController {
#IBOutlet var attendingAnswer: UISegmentedControl!
#IBOutlet var name : UILabel!
#IBOutlet weak var contactImage: UIImageView!
#IBOutlet weak var email: UILabel!
#IBOutlet weak var attending : UILabel!
var contact : CNContact?
var defaults : NSUserDefaults = NSUserDefaults.standardUserDefaults()
override func viewDidLoad() {
super.viewDidLoad()
let formatter = CNContactFormatter()
if let contact = contact {
name.text = (contact.phoneNumbers.first?.value as! CNPhoneNumber).stringValue
email.text = (contact.emailAddresses.first?.value as? String)
defaults.setValue(true, forKey: name.text!)
defaults.valueForKey(name.text!)
}
// Do any additional setup after loading the view.
}
#IBAction func segmentedControlAction(sender: AnyObject) {
if(segementControl.selectedSegmentIndex == 0)
{
let segmentControl = NSUserDefaults.standardUserDefaults()
segmentControl.setBool(true, forKey: "KeyName")
}
else if(segementControl.selectedSegmentIndex == 1)
{
let segmentControl = NSUserDefaults.standardUserDefaults()
segmentControl.setBool(false, forKey: "KeyName")
}
//Fetch
let defaults = NSUserDefaults.standardUserDefaults()
let segementName = defaults.boolForKey("KeyName")
}
I have a ViewController which saves user inputs to CoreData and after the save is attempted displaying MBProgressHUD to state if the save was successful or not.
I have an AddNewViewController class
class AddNewViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate, UITextFieldDelegate {
#IBOutlet weak var inputErrorMessage: UILabel!
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var amountLabel: UILabel!
#IBOutlet weak var dayPicker: UIPickerView!
#IBOutlet weak var durationPicker: UIPickerView!
#IBOutlet weak var nameTextField: UITextField!
#IBOutlet weak var amountTextField: UITextField!
#IBOutlet weak var notesTextField: UITextField!
//variable to contrain the origin view controller
var originVC: String?
// variables to hold user input
var name: String?
var amount: Double?
var notes: String?
var durationDay: Double?
var durationType: String?
// The days and duration options to display in the pickers
var durationPickerDataSource = ["Day(s)","Week(s)","Month(s)","Year(s)"];
var dayPickerDataSource = ["1","2","3","4","5","6","7","8","9","10","11","12"];
#IBAction func saveButton(sender: AnyObject) {
CoreDataStatic.data.saveIncomeBudgetAndExpenses(originVC!, name: name!, amount: amount, durationDay: durationDay!, durationType: durationType!, notes: notes!)
}
/**
The number of columns in the picker view.
*/
func numberOfComponentsInPickerView(dayPickerView: UIPickerView) -> Int {
return 1
}
/**
The number of items in the picker view. Equal to the number of days(12) and duration options(4) .
*/
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if pickerView == durationPicker {
return durationPickerDataSource.count;
}
else {
return dayPickerDataSource.count;
}
}
/**
Gets the titles to use for each element of the picker view.
*/
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if pickerView == durationPicker{
durationType = durationPickerDataSource[row]
return durationType
}
else {
durationDay = Double(dayPickerDataSource[row])
return dayPickerDataSource[row]
}
}
/**
Display acknowledgement if the Income, Budget or Fixed Expense saved.
*/
func displayMessage(origin: String) {
var message : String
//Changes the message depending on what the user was trying to save.
if CoreDataStatic.data.saved == true {
message = "\(origin) saved!"
}
else if CoreDataStatic.data.saved == false {
message = "Error: \(origin) failed to save!"
}
else {
message = "Error!"
}
print(message)
//displays acknowledgement for 2 seconds.
/*let acknowledgement = MBProgressHUD.showHUDAddedTo(self.view, animated: true)
acknowledgement.mode = MBProgressHUDMode.Text
acknowledgement.label.text = message
acknowledgement.hideAnimated(true, afterDelay: 2)*/
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.durationPicker.dataSource = self;
self.durationPicker.delegate = self;
self.dayPicker.dataSource = self;
self.dayPicker.delegate = self;
}
A CoreData class:
struct CoreDataStatic {
static let data = CoreData()
}
public class CoreData {
var appDel : AppDelegate
//Manage a collection of managed objects.
let context : NSManagedObjectContext
//Describes an entity in Core Data.
let incomeEntity : NSEntityDescription
let budgetEntity : NSEntityDescription
let fixedExpenseEntity : NSEntityDescription
//Retrieve data from Core Data with the entity 'Scores'.
let income = NSFetchRequest(entityName: "Income")
let budget = NSFetchRequest(entityName: "Budget")
let fixedExpense = NSFetchRequest(entityName: "FixedExpenses")
//Set the key that needs updating which is always 'score'
let nameKeyToUpdate = "name"
let amountDayKeyToUpdate = "amountDay"
let amountWeekKeyToUpdate = "amountWeek"
let amountMonthKeyToUpdate = "amountMonth"
let amountYearKeyToUpdate = "amountYear"
let durationDayKeyToUpdate = "durationDay"
let durationTypeKeyToUpdate = "durationType"
let notesKeyToUpdate = "notes"
var saved : Bool?
func saveIncomeBudgetAndExpenses(origin: String, name: String, amountDay: Double, amountWeek: Double, amountMonth: Double, amountYear: Double, durationDay: Double, durationType: String, notes: String) {
//saving in enity depending on origin view controller
let entity : NSEntityDescription
if origin == "Income" {
entity = NSEntityDescription.entityForName("Income", inManagedObjectContext: context)!
}
else if origin == "Budget" {
entity = NSEntityDescription.entityForName("Budget", inManagedObjectContext: context)!
}
else {
entity = NSEntityDescription.entityForName("FixedExpenses", inManagedObjectContext: context)!
}
let saveNew = NSManagedObject(entity: entity,
insertIntoManagedObjectContext:context)
// add user input to the relevant entity
saveNew.setValue(name, forKey: nameKeyToUpdate)
saveNew.setValue(amountDay, forKey: amountDayKeyToUpdate)
saveNew.setValue(amountWeek, forKey: amountWeekKeyToUpdate)
saveNew.setValue(amountMonth, forKey: amountMonthKeyToUpdate)
saveNew.setValue(amountYear, forKey: amountYearKeyToUpdate)
saveNew.setValue(durationDay, forKey: durationDayKeyToUpdate)
saveNew.setValue(durationType, forKey: durationTypeKeyToUpdate)
saveNew.setValue(notes, forKey: notesKeyToUpdate)
do {
try context.save()
print("saved")
saved = true
}
catch _ {
print("didnt save")
saved = false
}
AddNewViewController().displayMessage(origin)
}
init(){
appDel = (UIApplication.sharedApplication().delegate as! AppDelegate)
context = appDel.managedObjectContext
incomeEntity = NSEntityDescription.entityForName("Income", inManagedObjectContext: context)!
budgetEntity = NSEntityDescription.entityForName("Budget", inManagedObjectContext: context)!
fixedExpenseEntity = NSEntityDescription.entityForName("FixedExpenses", inManagedObjectContext: context)!
}
}
This code runs and as expected however when the commented out section in the displayMessage() function is uncommented I get the following error:
"fatal error: unexpectedly found nil while unwrapping an Optional value"
due to the line self.durationPicker.dataSource = self; in the override viewDidLoad()
Any help would be appreciated.
Note* if i call the displayMessage() within the saveButton function the code works so unsure why it isn't working when calling the message from the CoreData class.
I am unsure if this is the correct way about it but i found a fix.
a variable (bool) was created called attemptSave which is defaulted to false.
within the saveIncomeBudgetAndExpenses try and catch, the attemptSave is changed to true.
The displayMessage() function is now called within both button clicks using an if statement to check if an attemptSave is yes, if so, call function.
I am able to save an integer and load it using NSUserdeafaults but when I load up the number and try to continue adding to that number it starts at 0.
Example: I press the button 7 times and switch to a different page. I go back and click load button and it days 7. When I click it again it says 1.
Here is my "press for money" code:
//Press for Money
#IBAction func MoneyPress(sender: AnyObject) {
Money += 1
var MoneyNumberString:String = String(format: "Dollars:%i", Money)
self.DollarsLabel.text = (string: MoneyNumberString)
let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(MoneyNumberString, forKey: "money")
defaults.synchronize()
}
Loading Code:
#IBAction func LoadTestButton(sender: UIButton) {
let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
var money = defaults.valueForKey("money") as? String
DollarsLabel.text! = money!
}
Please help if you know how to make it continue adding to that number.
Full Code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var MoneyButton: UIButton!
#IBOutlet weak var OptionButton: UIButton!
#IBOutlet weak var FeelingLuckyButton: UIButton!
#IBOutlet weak var TrophiesButton: UIButton!
#IBOutlet weak var DollarsLabel: UILabel!
#IBOutlet weak var BuyNowOne: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
var Money = Int(0)
//Press for Money
#IBAction func MoneyPress(sender: AnyObject) {
Money += 1
var MoneyNumberString:String = String(format: "Dollars:%i", Money)
self.DollarsLabel.text = (string: MoneyNumberString)
let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(MoneyNumberString, forKey: "money")
defaults.synchronize()
}
#IBAction func LoadTestButton(sender: UIButton) {
let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
var money = defaults.valueForKey("money") as? String
DollarsLabel.text! = money!
}
}
The problem is that you're setting Money to 0 when you actually want to set it to contain the value in your NSUserDefaults. To do that in your LoadTestButton method, try this:
#IBAction func LoadTestButton(sender: UIButton) {
let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
var money = defaults.valueForKey("money") as? String
DollarsLabel.text! = money!
// Set "Money" to contain the numbers in your "money"
// string converted to an Int
Money = money!.stringByTrimmingCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet).toInt()!
}