I am trying to build a simple dictionary at runtime in Swift. I am fairly new to Swift, but experienced in Obj-C (and missing it).
I am gathering some JSON data via a web service and looping through its elements. During this loop I need to build the dictionary. Here is the dictionary I need to generate
"gauge": {
"gaugeID" : "03185"
"name" : "SOME GAUGE NAME"
"cfs" : 8410
"stage" : 7.05
}
Since values for cfs and flow may not be present, I need to add these values to the dictionary conditionally.
I have declared the following dictionary
var dictEntry:[String:AnyObject]
Then as I loop through the dictionary I need to build each key-value and add it to the dictEntry dictionary. Every attempt I've made to do this fails. In Obj-C I could do the following:
[entryDict setValue:someValue forKey:#"cfs"];
How is this possible in Swift? Thanks!
Here you go:
dictEntry["cfs"] = someValue
Related
I am in the beginning stages of developing an open-source utility for storing state in the Bundle UserDefaults.
I'm encountering an issue when I add non-Codable data types to my Dictionary of [String: Any].
I need to be able to vet the data before trying to submit it, because the UserDefaults.set(_:) method won't throw any errors. It just crashes.
So I want to make sure that the Dictionary that I'm submitting is kosher.
I can't just check if it's Codable, because it can sometimes say that it isn't, when the struct is actually good. (It's a Dictionary<String, Any>, and I can cram all kinds of things in there).
I need to validate that the Dictionary can produce a plist. If this were ObjC, I might use one of the NSPropertyListSerialization methods to test the Dictionary, but it appears as if this set of methods is not available to Swift.
According to the UserDefaults docs, there are a specific set of types and classes that are "plist-studly."
I think testing each type in the list is unacceptable. I need to see if I can find a way to test that won't be screwed the first time Apple updates an OS.
Is there a good way to test a Dictionary<String, Any> to see if it will make UserDefaults.set(_:) puke?
The Property List type set of UserDefaults is very limited. The supported types are
NSString → Swift String
NSNumber → Swift Int, Double or Bool
NSDate → Swift Date
NSData → Swift Data
Arrays and dictionaries containing the 4 value types.
Any is not supported unless it represents one of the 4 value or 2 collection types.
Property List compliant collection types can be written to UserDefaults with PropertyListSerialization (even in Swift).
There are two protocols to serialize custom types to Data
Codable can serialize structs and classes.
NSCoding can serialize subclasses of NSObject.
All types in the structs/classes must be encodable and decodable (means conform to the protocol themselves).
The APIs of PropertyListSerialization / PropertyListEncoder/-Decoder and NSKeyed(Un)Archiver provide robust error handling to avoid crashes.
UPDATE[1]: And, just because I like to share, here's the actual completed project (MIT License, as is most of my stuff)
UPDATE: This is the solution I came up with. Even though I greenchecked vadian's excellent answer, I decided to get a bit more picky.
Thanks to matt pointing out that I was looking under the wrong sofa cushions for the keys, I found the Swift variant of NSPropertyListSerialization, and I use that to vet the top level of the tree. I suspect that I'll need to refactor it into a recursive crawler before I'm done, but this works for now.
Here's the code for the _save() method at the time of this writing. It works:
/* ################################################################## */
/**
This is a private method that saves the current contents of the _values Dictionary to persistent storage, keyed by the value of the "key" property.
- throws: An error, if the values are not all codable.
*/
private func _save() throws {
#if DEBUG
print("Saving Prefs: \(String(describing: _values))")
#endif
// What we do here, is "scrub" the values of anything that was added against what is expected.
var temporaryDict: [String: Any] = [:]
keys.forEach {
temporaryDict[$0] = _values[$0]
}
_values = temporaryDict
if PropertyListSerialization.propertyList(_values, isValidFor: .xml) {
UserDefaults.standard.set(_values, forKey: key)
} else {
#if DEBUG
print("Attempt to set non-codable values!")
#endif
// What we do here, is look through our values list, and record the keys of the elements that are not considered Codable. We return those in the error that we throw.
var valueElementList: [String] = []
_values.forEach {
if PropertyListSerialization.propertyList($0.value, isValidFor: .xml) {
#if DEBUG
print("\($0.key) is OK")
#endif
} else {
#if DEBUG
print("\($0.key) is not Codable")
#endif
valueElementList.append($0.key)
}
}
throw PrefsError.valuesNotCodable(invalidElements: valueElementList)
}
}
I want to use this function's prototype for adding vocabulary to SiriKit. Apple's documentation shows the following sample code:
let workoutNames = self.sortedWorkoutNames()
let vocabulary = INVocabulary.shared()
vocabulary.setVocabularyStrings(workoutNames, of: .workoutActivityName)
According to the documentation, I need to use an NSOrderedSet type for self.sortedWorkoutNames().
How to declare it and set it with an array of Strings?
EDIT: About the context project, I'm using Intents with Siri. The goal here, is to use specific word like 'Benchpress' or 'Chicken' just to start my workout app with an INStartWorkoutIntent already implemented and working like a charm.
If you look at Apple's sample code for SiriKit, you can tell that their function sortedWorkoutNames() returns an NSOrderedSet. Look at the type of workoutNames in the Objective-C version:
NSOrderedSet* workoutNames = [self sortedWorkoutNames];
In Swift that would be
let workoutNames: NSOrderedSet = self.sortedWorkoutNames()
If you have an array that you want to pass to INVocabulary.setVocabularyStrings(_:of:), you'd need to convert it to an ordered set. As #larme said in his comment, NSOrderedSet has an initializer that takes an array as input.
Your code might look like this:
let myVocab = ["Benchpress", "Squats", "Deadlift"]
let myVocabSet = NSOrderedSet(array: myVocab) //Convert myVocab to an NSOrderedSet
let vocabulary = INVocabulary.shared()
vocabulary.setVocabularyStrings(myVocabSet, of: .workoutActivityName)
How do you print all the content of NSUserDefaults?
I need to see everything that has been stored into NSUserDefaults. Is there a simple way to print that or to see it into the logs?
In Swift!
Thank you
Taken from - Retrieve UserDefaults in Swift
In Swift we can use the following:-
Swift 3.x & 4.x
For getting all keys & values:
for (key, value) in UserDefaults.standard.dictionaryRepresentation() {
print("\(key) = \(value) \n")
}
For retrieving the complete dictionary representation of user defaults:
print(Array(UserDefaults.standard.dictionaryRepresentation()))
For retrieving the keys:
// Using dump since the keys are an array of strings.
dump(Array(UserDefaults.standard.dictionaryRepresentation().keys))
For retrieving the values:
We can use dump here as well, but that will return the complete inheritance hierarchy of each element in the values array. If more information about the objects is required, then use dump, else go ahead with the normal print statement.
// dump(Array(UserDefaults.standard.dictionaryRepresentation().values))
print(Array(UserDefaults.standard.dictionaryRepresentation().values))
Swift 2.x
For retrieving the complete dictionary representation of user defaults:
print(NSUserDefaults.standardUserDefaults().dictionaryRepresentation())
For retrieving the keys:
print(NSUserDefaults.standardUserDefaults().dictionaryRepresentation().keys.array)
For retrieving the values:
print(NSUserDefaults.standardUserDefaults().dictionaryRepresentation().values.array)
for elem in NSUserDefaults.standardUserDefaults().dictionaryRepresentation() {
println(elem)
}
Here is the syntax in to get all standard user defaults in Swift 3
print(UserDefaults.standard().dictionaryRepresentation())
With Swift 3.0
print(UserDefaults.standard.dictionaryRepresentation())
Here is the syntax that provide more clarity.
let defaults = UserDefaults.standard
defaults.dictionaryRepresentation().map{print("\($0.key): \($0.value)")}
I seem to be having a problem trying to create an NSDictionary in Swift using one of the convenience intializers. My confusion though lies in the fact that the error says my function signature is wrong, yet I'm using the function signature that XCode autocompleted for me.
My Code:
var query = NSDictionary(objects: [kSecClass, kSecAttrService, kSecAttrAccount, kSecReturnData], forKeys: [kSecClassGenericPassword, "healthBIT.lastSync", key, true])
The XCode provided signature:
var query = NSDictionary(objects: <#[AnyObject]#>, forKeys: <#[AnyObject]#>)
The error when compiling is: Extra argument 'forKeys' in call
What am I missing here? Am I just too sleep deprived to see the obvious? Or is it just a stupid mistake derived from my relative inexperience with Swift?
PS: I am trying to use NSDictionary here instead of a normal Swift dict because Swift dicts can't store mixed types, and I need to pass this to the underlying C based Keychain API.
After a discussion in chat, it turned out the problem is about the key variable referenced from the code sample provided in the question, which comes from a for loop:
for key in lastSync {
...
}
the error is that key is a (key, value) tuple, and that's causing issues when using it in a NSDictionary (objc types cannot handle swift specific features, such as generics, tuples, etc.).
The problem is solved by expanding the tuple accordingly:
for (key, value) in lastSync {
...
}
or, if value is not used:
for (key, _) in lastSync {
...
}
I have an iOS app that uses Firebase and currently has a few dictionaries with keys that are NSDate objects. The obvious issue with this is that NSDate draws from the device's system time, which is not universal.
With that, what's the best way to get a server timestamp (similar to Firebase.ServerValue.TIMESTAMP for the Web API) using Firebase's iOS API so that I can sort my dictionary keys chronologically?
I'm also aware of the chronological nature of IDs generated by childByAutoID, but I can't figure out the proper way to sort these in code. While they may be returned in chronological order, any time something like allKeys is called on them, the order goes out the window.
Any help with this issue would be greatly appreciated!
Update: In Firebase 3.0 + Swift, you can use
FIRServerValue.timestamp(). In Objective-C this is [FIRServerValue timestamp].
In Swift, you can now use FirebaseServerValue.timestamp() with Firebase 2.0.3+ (before 3.0).
The equivalent for Firebase.ServerValue.TIMESTAMP in iOS is kFirebaseServerValueTimestamp. Right now, this only works for Objective-C and not Swift.
In Swift, you can create your own global timestamp with
let kFirebaseServerValueTimestamp = [".sv":"timestamp"]
and then you'll be able to use kFirebaseServerValueTimestamp in the same way.
But you can only use this as the value or priority of a node. You won't be able to set it as the key name (although, I don't believe you could in the Web API either).
In general, calling allKeys on a dictionary does not guarantee order. But if you're using childByAutoID at a node, you can get back the right order by ordering the NSArray returned by allKeys lexicographically. Something like this would work:
[ref observeEventType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
NSDictionary *value = snapshot.value;
NSLog(#"Unsorted allKeys: %#", value.allKeys);
NSArray *sortedAllKeys = [value.allKeys sortedArrayUsingSelector:#selector(compare:)];
NSLog(#"Sorted allKeys: %#", sortedArray);
}];
This is similar to sorting an NSArray alphabetically, but when sorting the auto-generated IDs, you do not want localized or case insensitive sort, so you use compare: instead of localizedCaseInsensitiveCompare:
Caveat: Seems like the timestamp is added AFTER your object is persisted in Firebase. This means that if you have a .Value event listener set up on the location your object is persisted to, it will be triggered TWICE. Once for the initial object being stored in the location, and again for the timestamp being added. Struggled with this issue for days :(
Helpful information for anyone else who can't figure out why their event listeners are triggering twice/multiple times!
As of Firebase 4.0 you can use ServerValue.timestamp()
for example:
let ref = Database.database().reference().child("userExample")
let values = ["fullName": "Joe Bloggs", "timestamp": ServerValue.timestamp()] as [String : Any]
ref.updateChildValues(values) { (err, ref) in
if let err = err {
print("failed to upload user data", err)
return
}
}
You can get Time Stamp using FIRServerValue.timestamp().
But, Because of FIRServerValue.timestamp() listener is called two times. Listener will be called two times.