Swift 3 UserDefaults not saving - swift

Totally stumped on why the below code isn't saving correctly. I have gone through and printed each variable individually to make sure they all have data and all the way to latestBottle everything is fine. But when I try to pull the value back from UserDefaults, it's 0.0.
func saveBottleEntry() {
let allBottleEntries = realm.objects(SubmittedEntry.self).filter("bottleQuantity > 0")
for bottle in allBottleEntries {
bottleTimes.append(bottle.submissionTime!)
}
latestBottle = bottleTimes.max()!
UserDefaults.standard.set(latestBottle, forKey: "latestBottle")
print(UserDefaults.standard.double(forKey: "latestBottle"))
}
bottleTimes is an array of dates being retrieved from a Realm database. I then take the newest date bottleTimes.max() and save that to UserDefaults.

If you see 0.0 when try to get double that means that you wrote wrong type to UserDefaults

Related

Dictionary data structure returns nil in Swift

I am trying to implement a dictionary data structure in swift that stores an Array of Strings. I have declared it like:
var journeyDetails = [Int: [String]]()
When I want to append an actual string to it, I do
if let journeys = fetchedData["journeys"] as? [[String: Any]]{
var nr_of_journey : Int = 0
for journey in journeys{
self.journeyDetails[nr_of_journey]?.append("The starting date and time of the journey are: "+String(describing: journey["startDateTime"]))
}
}
nr_of_journey = nr_of_journey + 1
etc etc. However, journeyDetails keeps returning nil. Should I do any other type of initialization? Why is the data not appended?
Initially there are no keys or values in journeyDetails so every use of self.journeyDetails[nr_of_journey] returns nil.
If you are using Swift 4, you can specify a default value to be used if there currently isn't a value for the given key.
Update the line:
self.journeyDetails[nr_of_journey]?.append("The starting date and time of the journey are: "+String(describing: journey["startDateTime"]))
to:
self.journeyDetails[nr_of_journey, default: []].append("The starting date and time of the journey are: "+String(describing: journey["startDateTime"]))
This provides a default empty array if there currently isn't a value for the given nr_of_journey key.

Retrieving NSOrderedSet from Core Data and casting it to entity managedObjectSubclasss

Im making a Fitness app to learn Core data, and I have found that I need to let the user store every performed workout as a WorkoutLog item, and in there, there should be a series of ExerciseLogs which represents performances of that exercise (it contains each lift and also a reference to the actual exercise design).
Problem is that after a while i realize that i need to have these ordered, so that the next time i want to show the user their workout, the order that the exercisese were performed should be the same.
So I checked "ordered" in the top right of the image there, and now my code is in dire need of an update. I have tried to read as much as I could about working with NSOrderedSet and how to fetch them from core data and then manipulate them, but I havent really found much of use to me. (I have no experice in objective-c)
For example my code that used to be:
static func deleteWorkoutLog(_ workoutLogToDelete: WorkoutLog) {
guard let exerciseLogsToDelete = workoutLogToDelete.loggedExercises as? Set<ExerciseLog> else {
print("error unwrapping logged exercises in deleteWorkoutLog")
return
}
I get the error: .../DatabaseFacade.swift:84:77: Cast from 'NSOrderedSet?' to unrelated type 'Set' always fails
So what ive learned about sets and core data no longer seems applicable.
Im far from an expert in programming, but im very eager to learn how to get access to the loggedExercises instances.
TLDR; Is there a way to cast NSOrderedSet to something I can work with? How do we usually work with NSManagedSets from core data? Do we cast them to Arrays or MutableSets? I would very much appreciate an example or two on how to get started with retrieving and using these ordered sets!
Thanks
For anyone else wondering how to get started with orderedSets in core data:
After setting my the WorkoutLog.loggedExercises "to-many" relationship to be ordered, I managed to access them through the mutableOrderedSetValue function like this:
static func deleteWorkoutLog(_ workoutLogToDelete: WorkoutLog) {
let orderedExerciseLogs: NSMutableOrderedSet = workoutLogToDelete.mutableOrderedSetValue(forKey: "loggedExercises")
let exerciseLogsToDelete = orderedExerciseLogs.array
for exerciseLog in exerciseLogsToDelete {
guard let exerciseLog = exerciseLog as? ExerciseLog else {
return
}
Works great so far.
And to rearrange the NSOrderedSet I ended up doing something like this:
// Swap the order of the orderedSet
if let orderedExerciseLogs: NSOrderedSet = dataSourceWorkoutLog.loggedExercises {
var exerciseLogsAsArray = orderedExerciseLogs.array as! [ExerciseLog]
let temp = exerciseLogsAsArray[indexA]
exerciseLogsAsArray[indexA] = exerciseLogsAsArray[indexB]
exerciseLogsAsArray[indexB] = temp
let exerciseLogsAsOrderedeSet = NSOrderedSet(array: exerciseLogsAsArray)
dataSourceWorkoutLog.loggedExercises = exerciseLogsAsOrderedeSet
}

Saving integer variable locally

This is my code for setting the defaults.
override func viewDidLoad() {
super.viewDidLoad()
let defaults = UserDefaults.standard
defaults.set("\(coins) $", forKey: "labelName") }
coins is my integer variable. It increases every time someone clicks a button.
labelName is my label that shows how many coins are earned.
How to make it so the number of coins are saved locally and then updated when someone restarts the app?
I wouldn't save coins as a string which is what you're doing here:
defaults.set("\(coins) $", forKey: "labelName")
Instead save it as an Integer:
// Set data (whenever you change the value)
var coins = 100
UserDefaults.standard.set(coins, forKey: "Money")
When you want to get the data back (in viewDidLoad perhaps):
// Get Data
coins = UserDefaults.standard.integer(forKey: "Money")
I think you can save coins as an integer instead of a string, but then if you are using it as a string then you would have that change it back to a string after you accessed the data when you want to use it. If that makes sense.

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: Dictionary, Remove key and value that are nil

I have a dictionary that a fetch request is returning. This dictionary is then made in to an array of dates from [String: NSDate]. This dictionary has a value that is [:]. I cannot do anything to remove it. Can anyone help because I have spent two nights trying everything.
let results = try managedObjectContext.executeFetchRequest(fetchRequest) as! [[String:NSDate]]
print("results \(results)")
dates = results.map { $0["savedTime"]! as NSDate }
This failed due to the savedTime Key being nil
print result is
[["savedTime": 2016-07-19 23:00:00 +0000], [:]]
This construct $0["savedTime"]! is wrong. Putting the ! on the object means you know that the object will always be there, but it isn't always there. You are lying to the compiler so it is crashing. Try removing the !.
Also, putting the as NSDate is unnecessary because you already told the compiler that the values are NSDates in the line above. Lastly, since not all the dictionaries have the correct key, you need to remove any that don't. There are a couple of ways to do that, one would be to filter out the nils. Another is to use flatMap which converts and filters out nils at the same time.
Then you end up with the below.
let results = try managedObjectContext.executeFetchRequest(fetchRequest) as! [[String:NSDate]]
print("results \(results)")
dates = results.flatMap { $0["savedTime"] }
I'm worried though that even the above, although it compiles and runs, might not be what you really need. The array of dictionaries is a rather odd thing to be pulling out of a managedObjectContext...