Every time I run the app, and then re-run it, it saves the same items into the NSUserDefaults even if it is already there.
I tried to fix that with contains code, but it hasn't worked.
What am I missing?
for days in results! {
let nD = DayClass()
nD.dayOfTheWeek = days[āDā] as! String
let defaults = NSUserDefaults.standardUserDefaults()
if var existingArr = defaults.arrayForKey("D") as? [String] {
if existingArr.contains(days["D"] as! String) == false {
existingArr.append(nd.dayOfTheWeek)
}
} else {
defaults.setObject([nD.dayOfTheWeek], forKey: "D")
}
}
Every time I run the app, and then re-run it, it saves the same items into the NSUserDefaults even if it is already there.
Yes, because that's exactly what your code does:
defaults.setObject(existingArr, forKey: "D")
But what is existingArr? It's the defaults you've just loaded before:
if var existingArr = NSUserDefaults.standardUserDefaults().arrayForKey("D") as? [String]
So what happens is that when you enter the .contains branch, you always do the same operation: you save the existing array.
Contrary to what your comment in the code states, you're not appending anything to any array in that block, you're saving the existing one.
Related
I am not sure why my NSDictionary values are returning nil even after printing "employeeUserData" after I assign the firebase snapshot to it and seeing that the values are there. When I run my app to test if I can pull the Strings from the NSDictionary it is saying that it is a nil value. I've used this code before and this method has worked, not sure why all of a sudden it's not working and returning nil, and yes this is coding and stuff like this happens all the time lol just trying to understand why this particular problems is happening. Thanks.
let employeeUid = Auth.auth().currentUser?.uid
var employeeUserData: NSDictionary?
override func viewDidLoad() {
super.viewDidLoad()
Database.database().reference().child("employees").child(employeeUid!).child("Business").observe(DataEventType.value, with: { (snapshot) in
print("VALUE CHANGED IN USER_PROFILES")
self.employeeUserData = snapshot.value as? NSDictionary
//store the key in the users data variable
self.employeeUserData?.setValue(employeeUid, forKey: "uid")
print(self.employeeUserData!)
}) { (error) in
print(error.localizedDescription)
}
printUserData()
}
func printUserData() {
print(self.employeeUserData!["businessuid"] as! String)
}
It's because you're fetching the data from the database asynchronously (which is the correct way to do it) but you are calling printUserData synchronously. Because of that, printUserData() gets called before the actual data gets fetched & set.
When you run this code, notice you're seeing the print in printUserData() execute before print(self.employeeUserData) gets called.
I have an app where I time myself and see how long it takes me to complete a bunch of questions. I have the time transferred to another VC and displayed in a label. I have it being stored by pressing a button but when i have a new variable(time) it replaces it. How do i store an Array of values and that can be displayed in a label?
Button to save the value:
#IBAction func saveScore(_ sender: Any) {
scoreLabel.text = label.text
UserDefaults.standard.set(scoreLabel.text, forKey: "score")
}
The code that permanently holds the data:
override func viewDidAppear(_ animated: Bool) {
if let x = UserDefaults.standard.object(forKey: "score") as? String {
scoreLabel.text = x
}
}
My scoreLabel displays all my scores and label shows the time you just got.
Use the following extentions on UserDefaults to store an array of times:
extension UserDefaults {
var times: [String] {
get {
if let times = UserDefaults.standard.object(forKey: "times") as? [String] {
return times
} else {
return []
}
}
set {
UserDefaults.standard.set(newValue, forKey: "times")
}
}
}
While you don't need to extend UserDefaults, using extension can simplify a bit working with persisted values and it makes the code cleaner.
Then at the point where you show the data, use the following line to access the array:
let arrayOfTimes = UserDefaults.standard.times
scoreLabel.text = "\(arrayOfTimes)" // or any other formatting you'd like
And instead of setting the times to persist a new score, just add the new score to the array, e.g.:
// This will not only add the scoreLabel.text to the array, but also persists it
UserDefaults.standard.times.append(scoreLabel.text)
In Swift 4,
To save an array to User Defaults you would do:
let defaults = UserDefaults.standard
let array = [25, 50]
defaults.set(array, forKey: "Scores")
And to access the array from User Defaults:
let defaults = UserDefaults.standard
let retrievedArray = defaults.array(forKey: "Scores") as? [Int] ?? []
And if you were to display a score of your array in a label then, you would just do:
scoreLabel.text = String(describing: retrievedArray[0])
If you are using integers for your scoring system, I would suggest you
storing your scores as Int in User Defaults.
If you prefer using Strings though, please note that you can use the User Defaults' stringArray(forKey:) method directly, instead of the array(forKey:) method, and therefore, in that case, you wouldn't need to type cast your array:
let someStringArray = defaults.stringArray(forKey: "ArrayOfStrings")
Note: To answer your question, I will consider that you are using Int scores, but feel free to use whichever you prefer.
If you want to store your array to the same key in User Defaults every time you get a new score, you could do it easily like this:
let defaults = UserDefaults.standard
// Your new score:
let newScore = 75
// Get your current scores list from User Defaults:
var currentArray = defaults.array(forKey: "Scores") as? [Int] ?? []
// Append your new score to the current array:
let updatedArray = currentArray.append(newScore)
// And save your updated array to User Defaults:
defaults.set(updatedArray, forKey: "Scores")
// In this example, your User Defaults now contains the updated array [25, 50, 75]
And that's it :).
Please note that there is no need to use an extension for that..
UPDATE: Also, if you want to add something inside your viewDidAppear method, don't forget to add super.viewDidAppear(animated). The same goes for viewDidLoad, etc.
The documentation states:
You can override this method to perform additional tasks associated
with presenting the view. If you override this method, you must call
super at some point in your implementation.
So you would have:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let retrievedArray = defaults.array(forKey: "Scores") as? [Int] {
print(retrievedArray)
// You can access your scores array safely here
}
}
OverView
I continue to run into issues with adding multiple values to my Core Data entity. All i need to do is simply add 6 string-value items from a text field into Core Data. Specific examples/critique of my code would be very appreciated, as i am nearing mental break down with this issue.
The Issue
The first time i ran this, i tested it by saving only the first line (the product name) to core data and then printing it off. It worked perfect. After that, i tried the same method for all of them, and then tried printing. My program would set a breakpoint next to the "entity1.setValue(three, forKey: "serialNo")."
I also get a message in the debugger area that says (lldb).
If i try to step through the breakpoint, everything just prints out as 'nil'.
CODE
#IBAction func saveButton(sender: AnyObject) {
let appDel: AppDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
let context:NSManagedObjectContext = appDel.managedObjectContext
let entity1 = NSEntityDescription.insertNewObjectForEntityForName("UsedInfo", inManagedObjectContext:context) as NSManagedObject
let one = pickerTextField.text
let two = modelName.text
let three = serialNo.text
let four = YOM.text
let five = engineHours.text
let six = locationOfMachine.text
entity1.setValue(one, forKey: "product")
entity1.setValue(two, forKey:"modelName")
entity1.setValue(three, forKey:"serialNo")
entity1.setValue(four, forKey:"yom")
entity1.setValue(five, forKey:"engineHours")
entity1.setValue(six, forKey:"location")
print(entity1.valueForKey("product"))
print(entity1.valueForKey("modelName"))
print(entity1.valueForKey("serialNo"))
print(entity1.valueForKey("yom"))
print(entity1.valueForKey("engineHours"))
do {
try context.save()
}
catch {
print("error")
}
}
My current class has around 50 lines just encoding and decoding variables in order for my class to be NSUserDefaults compatible. Is there a better way to handle this?
Example:
init(coder aDecoder: NSCoder!) {
lightEnabled = aDecoder.decodeBoolForKey("lightEnabled")
soundEnabled = aDecoder.decodeBoolForKey("soundEnabled")
vibrateEnabled = aDecoder.decodeBoolForKey("vibrateEnabled")
pulseEnabled = aDecoder.decodeBoolForKey("pulseEnabled")
songs = aDecoder.decodeObjectForKey("songs") as! [Song]
currentSong = aDecoder.decodeIntegerForKey("currentSong")
enableBackgroundSound = aDecoder.decodeBoolForKey("enableBackgroundSound")
mixSound = aDecoder.decodeBoolForKey("mixSound")
playSoundInBackground = aDecoder.decodeBoolForKey("playSoundInBackground")
duckSounds = aDecoder.decodeBoolForKey("duckSounds")
BPMBackground = NSKeyedUnarchiver.unarchiveObjectWithData(aDecoder.decodeObjectForKey("BPMBackgorund") as! NSData) as! UIColor!
BPMPulseColor = NSKeyedUnarchiver.unarchiveObjectWithData(aDecoder.decodeObjectForKey("BPMPulseColor") as! NSData) as! UIColor!
TempoBackGround = NSKeyedUnarchiver.unarchiveObjectWithData(aDecoder.decodeObjectForKey("TempoBackGround") as! NSData) as! UIColor!
TempoPulseColor = NSKeyedUnarchiver.unarchiveObjectWithData(aDecoder.decodeObjectForKey("TempoPulseColor") as! NSData) as! UIColor!
TimeBackGround = NSKeyedUnarchiver.unarchiveObjectWithData(aDecoder.decodeObjectForKey("TimeBackGround") as! NSData) as! UIColor!
TimeStrokeColor = NSKeyedUnarchiver.unarchiveObjectWithData(aDecoder.decodeObjectForKey("TimeStrokeColor") as! NSData) as! UIColor!
TextColor = NSKeyedUnarchiver.unarchiveObjectWithData(aDecoder.decodeObjectForKey("TextColor") as! NSData) as! UIColor!
}
func encodeWithCoder(aCoder: NSCoder!) {
aCoder.encodeBool(lightEnabled, forKey: "lightEnabled")
aCoder.encodeBool(soundEnabled, forKey: "soundEnabled")
aCoder.encodeBool(vibrateEnabled, forKey: "vibrateEnabled")
aCoder.encodeBool(pulseEnabled, forKey: "pulseEnabled")
aCoder.encodeObject(songs, forKey: "songs")
aCoder.encodeInteger(currentSong, forKey: "currentSong")
aCoder.encodeBool(enableBackgroundSound, forKey: "enableBackgroundSound")
aCoder.encodeBool(mixSound, forKey: "mixSound")
aCoder.encodeBool(playSoundInBackground, forKey: "playSoundInBackground")
aCoder.encodeBool(duckSounds, forKey: "duckSounds")
aCoder.encodeObject(BPMBackground.archivedData(), forKey: "BPMBackground")
aCoder.encodeObject(BPMPulseColor.archivedData(), forKey: "BPMPulseColor")
aCoder.encodeObject(TempoBackGround.archivedData(), forKey: "TempoBackGround")
aCoder.encodeObject(TempoPulseColor.archivedData(), forKey: "TempoPulseColor")
aCoder.encodeObject(TimeBackGround.archivedData(), forKey: "TimeBackGround")
aCoder.encodeObject(TimeStrokeColor.archivedData(), forKey: "TimeStrokeColor")
aCoder.encodeObject(TextColor.archivedData(), forKey: "TextColor")
}
You should create a struct or enum to organise your keys, because your way is just prone to typos. Just put it right above your class
enum Key: String {
case allSettings
case lightEnabled
case soundEnabled
}
and than just call the keys like so
...forKey: Key.lightEnabled.rawValue)
Now in regards to your question, I was facing the same issue with my game trying to save properties for 40 levels (bestTimes, Level unlock status etc). I initially did what you tried and it was pure madness.
I ended up using arrays/dictionaries or even arrays of dictionaries for my data which cut down my code by like 80 percent.
Whats also nice about this is that say you need to save something like LevelUnlock bools, it will make your life so much easier later on. In my case I have a UnlockAllLevels button, and now I can just loop trough my dictionary/array and update/check the levelUnlock bools in a few lines of code. So much better than having massive if-else or switch statements to check each property individually.
For example
var settingsDict = [
Key.lightEnabled.rawValue: false,
Key.soundEnabled.rawValue: false,
...
]
Than in the decoder method you say this
Note: This way will take into account that you might add new values to the SettingsDict and than on the next app launch those values will not be removed because you are not replacing the whole dictionary with the saved one, you only update the values that already exist.
// If no saved data found do nothing
if var savedSettingsDict = decoder.decodeObjectForKey(Key.allSettings.rawValue) as? [String: Bool] {
// Update the dictionary values with the previously saved values
savedSettingsDict.forEach {
// If the key does not exist anymore remove it from saved data.
guard settingsDict.keys.contains($0) else {
savedSettingsDict.removeValue(forKey: $0)
return
}
settingsDict[$0] = $1
}
}
If you use multiple dictionaries than your decoder method will become a messy again and you will also repeat alot of code. To avoid this you can create an extension of NSCoder using generics.
extension NSCoder {
func decodeObject<T>(_ object: [String: T], forKey key: String) -> [String: T] {
guard var savedData = decodeObject(forKey: key) as? [String: T] else { return object }
var newData = object
savedData.forEach {
guard object.keys.contains($0) else {
savedData[$0] = nil
return
}
newData[$0] = $1
}
return newData
}
}
and than you can write this in the decoder method for each dictionary.
settingsDict = aDecoder.decodeObject(settingsDict, forKey: Key.allSettings.rawValue)
Your encoder method would look like this.
encoder.encodeObject(settingsDict, forKey: Key.allSettings.rawValue)
In your game/app you you can use them like so
settingsDict[Key.lightEnabled.rawValue] = true
if settingsDict[Key.lightEnabled.rawValue] == true {
/// light is turned on, do something
}
This way makes it also very easy to integrate iCloud KeyValue storage (just create an iCloud dictionary), again mainly because its so easy to save and compare a lot of values with very little code.
UPDATE:
To make calling these a bit easier I like to create some convenience getters/setters in the GameData class. This has the benefit that you can more easily call these properties in your project (like your old way) but your encode/decode method will still stay compact. You can also still do things such as looping to compare values.
var isLightEnabled: Bool {
get { return settingsDict[Key.lightEnabled.rawValue] ?? false }
set { settingsDict[Key.lightEnabled.rawValue] = newValue }
}
var isSoundEnabled: Bool {
get { return settingsDict[Key.soundEnabled.rawValue] ?? false }
set { settingsDict[Key.soundEnabled.rawValue] = newValue }
}
and than you can call them like normal properties.
isLightEnabled = true
if isLightEnabled {
/// light is turned on, do something
}
Look at protocol codeable in Swift 4.
The decoder and encoder will be auto-generated for you.
Check out: (starting about half way through)
https://developer.apple.com/videos/play/wwdc2017/212/
Before I start, just wanted to say I'm very new to app development in general, I've only been at this for a month, so feel free to dumb it down to me as much as possible haha.
Ok, so I'm working on a quote app and so I've created an array that I can access from any view controller. This that will contain "liked" quotes, which is added from another another view.
Here is my global "likedArray". It resides in its own swift file.
import Foundation
struct Globals {
static var likedArray: [String] = ["Touch 'Liked' To Continue..."]
}
Quotes are added to likedArray by this method, from another view controller file.
#IBAction func likedbuttonPressed(sender: AnyObject) {
Globals.likedArray.append(quoteLabel.text)
}
And "liked" quotes are shown in another view via this method
// Like button method to move forward in array
#IBAction func likedButtonTouched(sender: AnyObject) {
self.favouritesLabel.fadeOut(completion: {
(finished: Bool) -> Void in
self.currentlikedArrayIndex++
if self.currentlikedArrayIndex < Globals.likedArray.count {
self.favouritesLabel.text = Globals.likedArray[self.currentlikedArrayIndex]
} else {
self.currentlikedArrayIndex--
}
self.favouritesLabel.fadeIn()
})
}
And this all works fine, except that when I quit the app, all liked quotes are gone.
So, my question is how do I save this data?
If you need any more information about my code, please let me know. Thanks in advance.
The most hassle free way is probably to use NSUserDefaults, can follow this tutorial to find out how exactly
the jist of the tutorial:
//for writing to storage
let defaults = NSUserDefaults.standardUserDefaults()
let array = ["Hello", "World"]
defaults.setObject(array, forKey: "SavedArray")
//for reading
let array = defaults.objectForKey("SavedArray") as? [String] ?? [String]()
So basically, you should make a setter method for your global array so each time the array is set, it will write to the NSUserDefaults, then on app launch it should populate the array with whats in the NSUserDefaults
update: (just did this off the top of my head)
struct Globals {
static var likedArray: [String] = ["Touch 'Liked' To Continue..."]
func addLikedString(likedString: String) {
self.likedArray.append(likedString)
NSUserDefaults.standardUserDefaults().setObject(self.likedArray, forKey: "SavedArray")
}
func getLikedStringAtIndex(index:Int) -> Int {
return self.likedArray[index]
}
}
//inside your app delegate, or somewhere appropriate, to load the array when the app starts
Globals.likedArray = NSUserDefaults.standardUserDefaults().objectForKey("SavedArray") as? [String] ?? [String]()