Swift Userdefaults converting String to __NSCFString - swift

I have code that save a dictionary of [String: Any] in UserDefaults. On retrieval String are changed to __NSCFString. I am using Mixpanel to track events and sends this dictionary as events properties. Now the problem is __NSCFString is not a valid MixpanelType so Mixpanel is discarding my dictionary.
Questions:
Is there a way to get same datatypes that are saved using dictionary in UserDefaults?
Is there a way Mixpanel accepts converted datatypes?
Here is a code I am using
var mixpanelProperties: [String: Any] {
get { defaults.dictionary(forKey: "\(#function)") ?? [:] }
set { defaults.set(newValue, forKey: "\(#function)") }
}
mixpanelProperties = ["a-key": "value for the key"]
let prop = mixpanelProperties
print("Type of: \(String(describing: prop["a-key"]))")
asdad

MacOS and iOS use a variety of different classes to represent strings, which are all compatible. x as? String should just work, no matter what the concrete class of x is. Unless you use code that explicitely checks the class which you shouldn't do.

Related

String as Member Name in Swift

I have an array of strings and a CoreData object with a bunch of variables stored in it; the strings represent each stored variable. I want to show the value of each of the variables in a list. However, I cannot find a way to fetch all variables from a coredata object, and so instead I'm trying to use the following code.
ListView: View{
//I call this view from another one and pass in the object.
let object: Object
//I have a bunch of strings for each variable, this is just a few of them
let strings = ["first_name", "_last_name", "middle_initial" ...]
var body: some View{
List{
ForEach(strings){ str in
//Want to pass in string here as property name
object.str
//This doesn't work because string cannot be directly passed in as property name - this is the essence of my question.
}
}
}
}
So as you can see, I just want to pass in the string name as a member name for the CoreData object. When I try the code above, I get the following errors: Value of type 'Object' has no member 'name' and Expected member name following '.'. Please tell me how to pass in the string as a property name.
CoreData is heavily based on KVC (Key-Value Coding) so you can use key paths which is much more reliable than string literals.
let paths : [KeyPath<Object,String>] = [\.first_name, \.last_name, \.middle_initial]
...
ForEach(paths, id: \.self){ path in
Text(object[keyPath: path]))
}
Swift is a strongly typed language, and iterating in a python/javascript like approach is less common and less recommended.
Having said that, to my best knowledge you have three ways to tackle this issue.
First, I'd suggest encoding the CoreData model into a dictionary [String: Any] or [String: String] - then you can keep the same approach you wanted - iterate over the property names array and get them as follow:
let dic = object.asDictionary()
ForEach(strings){ str in
//Want to pass in string here as property name
let propertyValue = dic[str]
//This doesn't work because string cannot be directly passed in as property name - this is the essence of my question.
}
Make sure to comply with Encodable and to have this extension
extension Encodable {
func asDictionary() throws -> [String: Any] {
let data = try JSONEncoder().encode(self)
guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else {
throw NSError()
}
return dictionary
}
Second, you can hard coded the properties and if/else/switch over them in the loop
ForEach(strings){ str in
//Want to pass in string here as property name
switch str {
case "first_name":
// Do what is needed
}
}
Third, and last, You can read and use a technique called reflection, which is the closest thing to what you want to achieve
link1
link2

Top-level Bool encoded as number property list fragment. PropertyListEncoder

I have this generic function to save in NSUserDefaults, in generally works but now I want to save a boolean value and I get an error. I could not find anything and I do not understand why it is not working.
extension UserDefaults {
func saveUserDefaults<T: Codable>(withKey key: String, myType: T) throws{
do {
let data = try PropertyListEncoder().encode(myType)
UserDefaults.standard.set(data, forKey: key)
print("Saved for Key:", key)
} catch let error {
print("Save Failed")
throw error
}
}
I am calling it like this:
try! UserDefaults().saveUserDefaults(withKey: "String", myType: false)
This is the error I get. I know there is an other way to save boolean values, but I am wondering why it is not working like this?
Thread 1: Fatal error: 'try!' expression unexpectedly raised an error:
Swift.EncodingError.invalidValue(false,
Swift.EncodingError.Context(codingPath: [], debugDescription:
"Top-level Bool encoded as number property list fragment.",
underlyingError: nil))
Thanks!
A PropertyListEncoder encodes to a “property list,” and that is always an
array or a dictionary, compare PropertyListSerialization.
Therefore
let data = try PropertyListEncoder().encode(myType)
fails if myType is a Bool (or anything which is not an array
or a dictionary).
The possible objects in a property list are also restricted, they can only be instances of
NSData, NSString, NSArray, NSDictionary, NSDate, or NSNumber – or of Swift types
which are bridged to one of those Foundation types.
As #Martin said a PropertyListEncoder supports only property lists on top level, but not a single fragment of property list like NSNumber.
A very simple (though not very elegant) workaround is to wrap any object into array:
let data = try PropertyListEncoder().encode([myType])
UserDefaults.standard.set(data, forKey: key)
And decode it as:
let arr = try PropertyListDecoder().decode([T].self, from: data)
return arr.first
see https://www.marisibrothers.com/2018/07/workaround-for-serializing-codable-fragments.html
You do not need to encode a Boolean value to save into UserDefaults. You can directly save the boolean into the UserDefaults by calling
let myValue: Bool = false
UserDefaults.standard.set(myValue, forKey: "key")
See Apple docs: UserDefaults.set(_:forKey:).
In fact, Float, Double, Integer, Bool and URL types do not need to be encoded and can directly be saved to UserDefaults.
I see that you have a function that takes a Codable type, encodes it and then saves it to UserDefaults. As Martin R pointed out, you will have to modify that function and check whether passed object can be directly saved to UserDefaults without a need for encoding. It is not necessarily pretty but something like this could work:
switch objectToSave {
case let aFloatType as Float:
UserDefaults.standard.set(aFloatType, forKey: "key")
case let aDoubleType as Double:
UserDefaults.standard.set(aDoubleType, forKey: "key")
case let anIntegerType as Int:
UserDefaults.standard.set(anIntegerType, forKey: "key")
case let aBoolType as Bool:
UserDefaults.standard.set(aBoolType, forKey: "key")
case let aURLType as URL:
UserDefaults.standard.set(aURLType, forKey: "key")
default:
//encode the object as a PropertyList and then save it to UserDefaults
do {
let data = try PropertyListEncoder().encode(objectToSave)
UserDefaults.standard.set(data, forKey: key)
} catch let error {
//the object you passed cannot be encoded as a PropertyList
//possibly because the object cannot be represented as
//NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary - or equivalent Swift types
//also contents of NSArray and NSDictionary have to be one of the types above
print("Save Failed: \(error)")
//perform additional error handling
}
}

Iterating dictionary swift 3

I have below code in my project.
for (key, value) in photoDic {
if let url = URL.init(string: value as! String){
let photo : PhotoRecord = PhotoRecord.init(name:key as! String, url:url)
self.photoRecords.append(photo)
}
}
My question is how can I make key and value in for loop optional, or check if either of them are nil?
I am not able to check if they are nil, getting warning saying any cannot be nil because it is nonoptional.
I was thinking of using something like
for(key:String?, value:String?){}
But it is not working.
The key in a dictionary can't be an optional. (The key must conform to the Hashable protocol, and optionals don't.) So you CAN'T make the keys in your dictionary optional
If you want the values of your dictionary to be Optionals then you need to declare them as Optionals.
So, for example, change
let photoDic: [String: String] = ["key1": "http://www.someDomain.com/image.jpg"]
to
let photoDic: [String: String?] = ["key1": "http://www.someDomain.com/image.jpg"]
(Note that the type of photoDic is changed to [String: String?].)
As mentioned already all keys in a dictionary are non-optional by definition.
Further in NSDictionary all values are non-optional by definition, too.
Be happy about that because
There is no need to check for nil.
The code will never crash.
A Swift dictionary can theoretically contain optional values but practically you are discouraged from using it. For compatibility reasons to NSDictionary a nil value indicates key is missing.

Variable used within its own initial value Swift 3

I try to convert my code to swift 3 an I have spent hours on the following error:
Type 'Any' has no subscript members
Here's was my original code:
let data: AnyObject = user.object(forKey: "profilePicture")![0]
I looked at the answers here but I'm still stuck. (I do programming as a hobby, I'm not a pro :/)
I've try that:
let object = object.object(forKey: "profilePicture") as? NSDictionary
let data: AnyObject = object![0] as AnyObject
But now I get this error:
Variable used within its own initial value
Second issue: Use always a different variable name as the method name, basically use more descriptive names than object anyway.
First issue: Tell the compiler the type of the value for profilePicture, apparently an array.
if let profilePictures = user["profilePicture"] as? [[String:Any]], !profilePictures.isEmpty {
let data = profilePictures[0]
}
However, the array might contain Data objects, if so use
if let profilePictures = user["profilePicture"] as? [Data], !profilePictures.isEmpty {
let data = profilePictures[0]
}
Or – what the key implies – the value for profilePicture is a single object, who knows (but you ...)
And finally, as always, don't use NSArray / NSDictionary in Swift.

How to access CFDictionary in Swift 3?

I need to read and write some data from CFDictionary instances (to read and update EXIF data in photos). For the life of me, I cannot figure out how to do this in Swift 3. The signature for the call I want is:
func CFDictionaryGetValue(CFDictionary!, UnsafeRawPointer!)
How the heck do I convert my key (a string) to an UnsafeRawPointer so I can pass it to this call?
If you don't have to deal with other Core Foundation functions expecting an CFDictionary, you can simplify it by converting to Swift native Dictionary:
if let dict = cfDict as? [String: AnyObject] {
print(dict["key"])
}
Be careful converting a CFDictionary to a Swift native dictionary. The bridging is actually quite expensive as I just found out in my own code (yay for profiling!), so if it's being called quite a lot (as it was for me) this can become a big issue.
Remember that CFDictionary is toll-free bridged with NSDictionary. So, the fastest thing you can do looks more like this:
let cfDictionary: CFDictionary = <code that returns CFDictionary>
if let someValue = (cfDictionary as NSDictionary)["some key"] as? TargetType {
// do stuff with someValue
}
What about something like:
var key = "myKey"
let value = withUnsafePointer(to: &key){ upKey in
return CFDictionaryGetValue(myCFDictionary, upKey)
}
You can write something like this:
let key = "some key" as NSString
if let rawResult = CFDictionaryGetValue(cfDictionary, Unmanaged.passUnretained(key).toOpaque()) {
let result = Unmanaged<AnyObject>.fromOpaque(rawResult).takeUnretainedValue()
print(result)
}
But I guess you would not like to write such thing at any time you retrieve some data from that CFDictionary. You better convert it to Swift Dictionary as suggested in Code Different's answer.