How to convert between Key and String in Swift? - ios

I'm making an extension on Dictionary, just a convenience method to traverse a deep json structure to find a given dictionary that might be present. In the general extension of a Dictionary, i'm not able to subscript because i give a String instead of a Key
extension Dictionary {
func openingHoursDictionary() -> Dictionary<String,AnyObject>? {
if let openingHours = self["openingHours"] as? Array<AnyObject> {
// traverses further and finds opening hours dictionary
}
return nil
}
}
Error: String is not convertible to DictionaryIndex<Key, Value>
on self["openingHours"]
How can i make a Key from the String "openingHours" or check the dictionary for ths string?

You can check at runtime if the string is a valid key for the dictionary:
extension Dictionary {
func openingHoursDictionary() -> [String : AnyObject]? {
if let key = "openingHours" as? Key {
if let openingHours = self[key] as? Array<AnyObject> {
// traverses further and finds opening hours dictionary
}
}
return nil
}
}
But this will "silently" return nil if called for other dictionaries
like [Int, AnyObject].
If you want the compiler to check if it is safe to subscript the
dictionary with a string then you have to use a (generic) function:
func openingHoursDictionary<T>(dict : [String : T]) -> [String : AnyObject]? {
if let openingHours = dict["openingHours"] as? Array<AnyObject> {
// traverses further and finds opening hours dictionary
}
return nil
}
It is (currently) not possible to write Dictionary (or Array)
extension methods that apply only to a restricted type of the
generic parameters.

Just cast the String as Key using "openingHours" as Key
if let pickupPoints = self["openingHours" as Key] as? Array<AnyObject> {
}
Downside is that if doing this i will get a crash if i have a Dictionary<Int,AnyObject> and use the method there.
0x10e8c037d: leaq 0x362aa(%rip), %rax ; "Swift dynamic cast failure"

Related

Conditional cast from string to string always succeds in swift 3

func resetUserDefaults() {
let userDefaults = UserDefaults.standard
let dict = userDefaults.dictionaryRepresentation()
for (key,_) in dict {
if let key = key as? String {
userDefaults.removeObject(forKey: key)
} else {
#if DEBUG
NSLog("\(key)")
#endif
}
}
}
I'm getting this warning. can anyone suggest me how to avoid this warnoing
All keys in UserDefaults must be of type String. So key is declared as a String. So attempting to cast it to a String is pointless. Hence the warning.
All you need is:
func resetUserDefaults() {
let userDefaults = UserDefaults.standard
let dict = userDefaults.dictionaryRepresentation()
for (key,_) in dict {
userDefaults.removeObject(forKey: key)
}
}
There is no need to cast something to the type that it is already known (to the compiler) to have.
Just remove the whole condition and use your key directly.
Since the keys in the UserDefault should of type String, casting the key to string is of no use, and hence you are getting this warning.
func resetUserDefaults() {
let userDefaults = UserDefaults.standard
let dict = userDefaults.dictionaryRepresentation()
for (key, _) in dict {
userDefaults.removeObject(forKey: key)
}
}
It will always show waring because dictionaryRepresentation() return [String : Any].
So when you cast from string to string it will definitely show warning.
for more see this -> https://developer.apple.com/documentation/foundation/userdefaults/1415919-dictionaryrepresentation
I had the same issue with a private function in Swift 5 and I found a solution working for me.
The solution was to change the value to optional.
I added a question mark after the type I was looking for. (as String"?")
You can see an example here :
private func doSomeThing(completion: #escaping (String) -> ()) {
let Something = somethingElse;
if let anoterThing = something as String?{
completion(anoterThing)
}else{
completion("Error at private func doSomeThing")
}
}
You can find more pieces of information here:
https://docs.swift.org/swift-book/LanguageGuide/OptionalChaining.html
Swift: difference as String? vs. as? String
Downcasting in Swift with as and as?
Best Regards

Swift 2.1 Dictionary extension

I'm hoping to write a Swift dictionary extension that will append the reliably same data that eventually gets passed into a web request as URL params. I've tried various tricks, none seem to work.
The closest i've gotten is:
extension Dictionary where Key: StringLiteralConvertible, Value: AnyObject {
mutating func auth() -> Dictionary {
self.updateValue("2.0", forKey: "api")
return self
}
}
But this is still throwing the error:
Cannot invoke 'updateValue' with an argument list of type '(String,
forKey:String)'
on the line that reads, "self.updateValue(..."
Any advice? Thanks in advance!
You just need to cast your string as! Value or as! Key. Also you have declared your method as mutating so you don't need to return anything.
extension Dictionary {
mutating func auth() {
updateValue("2.0" as! Value, forKey: "api" as! Key)
}
}
var dic: [String:AnyObject] = [:]
print(dic) // "[:]\n"
dic.auth()
print(dic) // "["api": 2.0]\n"

How to pass a Type as a parameter in Swift

I have a dictionary of objects and what I would like to do is go through the data set and return an array of objects that conform to a given protocol. I am having issues with the syntax for passing in a desired protocol:
func getObjectsThatConformTo<T>(conformance: T.Type) -> [AnyClass]{
var returnArray: [AnyClass] = []
for(myKey, myValue) in allCreatedObjects{
if let conformantObject = myValue as? conformance{
returnArray.append(conformantObject)
}
return returnArray
}
The error I am seeing is 'conformance' is not a type
Thank you for your help and time
I think this should work:
func getObjectsThatConformToType<T>(type:T.Type) -> [T]{
var returnArray: [T] = []
for(myKey, myValue) in allCreatedObjects{
if let comformantModule = myValue as? T {
returnArray.append(comformantModule)
}
}
return returnArray
}
While you could write a generic-ed method that filters through an array and sees which things in the array are a given type, this problem screams for the use of filter.
Example:
var dict: [String: AnyObject] = [:]
// Populate dict with some values
let strings = dict.values.filter { return $0 is String }
Wrapped in a function that takes type:
func getObjectsThatConformTo<T>(array: [Any], conformance: T.Type) -> [T]? {
return array.filter { return $0 is T } as? [T]
}
Explanation:
Filter is a method on Array which returns a subset of the array based on a test. In this case our test is 'is the element a String?' the filter method accepts a closure with one parameter, the element to be tested, above referred to with $0.
Read up on filter here: https://www.weheartswift.com/higher-order-functions-map-filter-reduce-and-more/

Array from dictionary keys in swift

Trying to fill an array with strings from the keys in a dictionary in swift.
var componentArray: [String]
let dict = NSDictionary(contentsOfFile: NSBundle.mainBundle().pathForResource("Components", ofType: "plist")!)
componentArray = dict.allKeys
This returns an error of: 'AnyObject' not identical to string
Also tried
componentArray = dict.allKeys as String
but get: 'String' is not convertible to [String]
Swift 3 & Swift 4
componentArray = Array(dict.keys) // for Dictionary
componentArray = dict.allKeys // for NSDictionary
With Swift 3, Dictionary has a keys property. keys has the following declaration:
var keys: LazyMapCollection<Dictionary<Key, Value>, Key> { get }
A collection containing just the keys of the dictionary.
Note that LazyMapCollection that can easily be mapped to an Array with Array's init(_:) initializer.
From NSDictionary to [String]
The following iOS AppDelegate class snippet shows how to get an array of strings ([String]) using keys property from a NSDictionary:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let string = Bundle.main.path(forResource: "Components", ofType: "plist")!
if let dict = NSDictionary(contentsOfFile: string) as? [String : Int] {
let lazyMapCollection = dict.keys
let componentArray = Array(lazyMapCollection)
print(componentArray)
// prints: ["Car", "Boat"]
}
return true
}
From [String: Int] to [String]
In a more general way, the following Playground code shows how to get an array of strings ([String]) using keys property from a dictionary with string keys and integer values ([String: Int]):
let dictionary = ["Gabrielle": 49, "Bree": 32, "Susan": 12, "Lynette": 7]
let lazyMapCollection = dictionary.keys
let stringArray = Array(lazyMapCollection)
print(stringArray)
// prints: ["Bree", "Susan", "Lynette", "Gabrielle"]
From [Int: String] to [String]
The following Playground code shows how to get an array of strings ([String]) using keys property from a dictionary with integer keys and string values ([Int: String]):
let dictionary = [49: "Gabrielle", 32: "Bree", 12: "Susan", 7: "Lynette"]
let lazyMapCollection = dictionary.keys
let stringArray = Array(lazyMapCollection.map { String($0) })
// let stringArray = Array(lazyMapCollection).map { String($0) } // also works
print(stringArray)
// prints: ["32", "12", "7", "49"]
Array from dictionary keys in Swift
componentArray = [String] (dict.keys)
You can use dictionary.map like this:
let myKeys: [String] = myDictionary.map{String($0.key) }
The explanation:
Map iterates through the myDictionary and accepts each key and value pair as $0. From here you can get $0.key or $0.value. Inside the trailing closure {}, you can transform each element and return that element. Since you want $0 and you want it as a string then you convert using String($0.key). You collect the transformed elements to an array of strings.
dict.allKeys is not a String. It is a [String], exactly as the error message tells you (assuming, of course, that the keys are all strings; this is exactly what you are asserting when you say that).
So, either start by typing componentArray as [AnyObject], because that is how it is typed in the Cocoa API, or else, if you cast dict.allKeys, cast it to [String], because that is how you have typed componentArray.
extension Array {
public func toDictionary<Key: Hashable>(with selectKey: (Element) -> Key) -> [Key:Element] {
var dict = [Key:Element]()
for element in self {
dict[selectKey(element)] = element
}
return dict
}
}
dict.keys.sorted()
that gives [String]
https://developer.apple.com/documentation/swift/array/2945003-sorted
From the official Array Apple documentation:
init(_:) - Creates an array containing the elements of a sequence.
Declaration
Array.init<S>(_ s: S) where Element == S.Element, S : Sequence
Parameters
s - The sequence of elements to turn into an array.
Discussion
You can use this initializer to create an array from any other type that conforms to the Sequence protocol...You can also use this initializer to convert a complex sequence or collection type back to an array. For example, the keys property of a dictionary isn’t an array with its own storage, it’s a collection that maps its elements from the dictionary only when they’re accessed, saving the time and space needed to allocate an array. If you need to pass those keys to a method that takes an array, however, use this initializer to convert that list from its type of LazyMapCollection<Dictionary<String, Int>, Int> to a simple [String].
func cacheImagesWithNames(names: [String]) {
// custom image loading and caching
}
let namedHues: [String: Int] = ["Vermillion": 18, "Magenta": 302,
"Gold": 50, "Cerise": 320]
let colorNames = Array(namedHues.keys)
cacheImagesWithNames(colorNames)
print(colorNames)
// Prints "["Gold", "Cerise", "Magenta", "Vermillion"]"
Swift 5
var dict = ["key1":"Value1", "key2":"Value2"]
let k = dict.keys
var a: [String]()
a.append(contentsOf: k)
This works for me.
NSDictionary is Class(pass by reference)
Dictionary is Structure(pass by value)
====== Array from NSDictionary ======
NSDictionary has allKeys and allValues get properties with
type [Any].
let objesctNSDictionary =
NSDictionary.init(dictionary: ["BR": "Brazil", "GH": "Ghana", "JP": "Japan"])
let objectArrayOfAllKeys:Array = objesctNSDictionary.allKeys
let objectArrayOfAllValues:Array = objesctNSDictionary.allValues
print(objectArrayOfAllKeys)
print(objectArrayOfAllValues)
====== Array From Dictionary ======
Apple reference for Dictionary's keys and values properties.
let objectDictionary:Dictionary =
["BR": "Brazil", "GH": "Ghana", "JP": "Japan"]
let objectArrayOfAllKeys:Array = Array(objectDictionary.keys)
let objectArrayOfAllValues:Array = Array(objectDictionary.values)
print(objectArrayOfAllKeys)
print(objectArrayOfAllValues)
This answer will be for swift dictionary w/ String keys. Like this one below.
let dict: [String: Int] = ["hey": 1, "yo": 2, "sup": 3, "hello": 4, "whassup": 5]
Here's the extension I'll use.
extension Dictionary {
func allKeys() -> [String] {
guard self.keys.first is String else {
debugPrint("This function will not return other hashable types. (Only strings)")
return []
}
return self.flatMap { (anEntry) -> String? in
guard let temp = anEntry.key as? String else { return nil }
return temp }
}
}
And I'll get all the keys later using this.
let componentsArray = dict.allKeys()
// Old version (for history)
let keys = dictionary.keys.map { $0 }
let keys = dictionary?.keys.map { $0 } ?? [T]()
// New more explained version for our ducks
extension Dictionary {
var allKeys: [Dictionary.Key] {
return self.keys.map { $0 }
}
}

Swift[String: AnyObject] not convertible to T Swift Arrays

I am working inside a Swift Extension. I am trying to append data to an array of the type [[String: AnyObject]]. The reason that this is in an extension is because I have to do this lot's of times to lot's of arrays. The problem is, when I append an object of type: [String: AnyObject], I get the error: Dictionary'<'String, AnyObject'>' Not Convertible to T (the quotes are there because within the carrots nothing showed up).
mutating func appendData(data: [String: [String: AnyObject]]?) {
if data != nil {
for (id, object) in data! {
var mutatingObject = object
mutatingObject["id"] = id
append(mutatingObject)
}
}
}
I am not certain what exactly are you trying to achieve. but take a note - Arrays are generic collections that store specific type. Extension for Array might not know what type will be used in each case, so it cannot simply allow you to store Dictionary<String, AnyObject>.
Here is an example on how to make your code more generic:
extension Array {
mutating func appendData(data: [String: T]?) {
if data != nil {
for (id, object) in data! {
if var mutatingObject = object as? [String : AnyObject] {
mutatingObject["id"] = id
}
append(object)
}
}
}
}

Resources