Why is Swift Dictionary not bridgeable? - swift

Dictionary is a bridged type, why is it that I can switch from Swift dictionary to NSDictionary but not the other way around? (Compile Error: NSDictionary is not convertible to 'Dictionary')
According to Apple's doc:
All NSDictionary objects can be bridged to Swift dictionaries, so the Swift compiler replaces the NSDictionary class with [NSObject: AnyObject] when it imports Objective-C APIs.
import Foundation
var swiftDict = ["0": "zero"]
let nsDict:NSDictionary = swiftDict
let backToSwiftDict:Dictionary = nsDict

This is correct but you have to perform a type safe cast from NSDictionary to a Dictionary
var swiftDict = ["0": "zero"]
let nsDict: NSDictionary = swiftDict
let backToSwiftDict: Dictionary = nsDict as Dictionary

... or you can cast it back into a dictionary with type-safe fields ...
var swiftDict = ["0": "zero"]
let nsDict:NSDictionary = swiftDict
let backToSwiftDict = nsDict as! [String:String]

Related

NSNumber swift Dictionary to userdefaults swift3

I am trying to save swift dictionary [NSNumber : NSNumber] to UserDefaults. I have already tried to cast it as NSDictinonary, but still, the application crashes when I use set() function.
Key is a beacon minor key (NSNumber)
Value is an NSTimeinterval cast as NSNumber
the crash:
libc++abi.dylib: terminating with uncaught exception of type
NSException
var recentBeacons: [NSNumber : NSNumber] = [:]
func saveRecentBeaconDict()
{
let recentBeaconKeys = recentBeacons.keys
print("keys type is \(String(describing: recentBeaconKeys.self))")
let recentBeaconsNSDict = recentBeacons as NSDictionary
UserDefaults.standard.set(recentBeaconsNSDict, forKey:"recentBeacons")
}
prints out: keys type is LazyMapCollection< Dictionary < NSNumber, NSNumber >, NSNumber >(_base: [46171: 1501585588.173543], _transform: (Function))
Try with below answer.
var recentBeacons: [NSNumber : NSNumber] = [:]
func saveRecentBeaconDict()
{
let archivedObject = NSKeyedArchiver.archivedData(withRootObject: recentBeacons as NSDictionary)
UserDefaults.standard.set(archivedObject, forKey: "recentBeacons")
let data = UserDefaults.standard.object(forKey: "recentBeacons") as? Data
if let _data = data {
let allData = NSKeyedUnarchiver.unarchiveObject(with: _data) as! NSDictionary
print(allData)
}
}
You can't save Dictionary of type [NSNumber : NSNumber], allowed only [String, AnyObject] or [NSObject : AnyObject].
As approach you can convert your NSNumber's to String's

How do I convert NSDictionary to Dictionary?

I have already updated to XCode 8 and now I need to convert my code from Swift 2 to Swift 3.
Before, when I want to convert NSDictionary to Dictionary, I just wrote the following:
let post_paramsValue = post_params as? Dictionary<String,AnyObject?>
where post_params is NSDictionary.
But now with Swift 3, I am receiving this error:
NSDictionary is not convertible to Dictionary
Why? What's changed?
Edit 1
I've also tried the following:
let post_paramsValue = post_params as Dictionary<String,Any>
But that gives this error:
Edit 2
I've also tried the following:
let post_paramsValue = post_params as Dictionary<String,Any>
Where I declare NSDictionary instead of NSDictionary!, but it doesn't work; I got this error:
Edit 3
I've also tried the following:
let post_paramsValue = post_params as Dictionary<String,Any>!
But I received this error:
NSDictionary in Objective-C has always non-optional values.
AnyObject has become Any in Swift 3.
Considering the first two "rules" NSDictionary can be bridge cast to Dictionary
let post_paramsValue = post_params as Dictionary<String,Any>
If the source NSDictionary is an optional you might use as Dictionary<String,Any>? or as? Dictionary<String,Any> or as! Dictionary<String,Any> or as Dictionary<String,Any>! depending on the actual type of the NSDictionary
For those who have a problem with NSDictionary, simply use this extension:
Swift 3.0
extension NSDictionary {
var swiftDictionary: Dictionary<String, Any> {
var swiftDictionary = Dictionary<String, Any>()
for key : Any in self.allKeys {
let stringKey = key as! String
if let keyValue = self.value(forKey: stringKey){
swiftDictionary[stringKey] = keyValue
}
}
return swiftDictionary
}
}
You just need to declare the NSDictionary properly in objc
For example: NSDictionary<NSString *, NSString*> gets translated automatically to [String: String] in swift interfaces

CFDictionary won't bridge to NSDictionary (Swift 2.0 / iOS9)

OK, this is a case I came across when working with CGImageSource and noticed that the toll-free-bridging between CFDictionary and NSDictionary seems to run into problems in certain cases. I've managed to construct the below example to show what I mean:
func optionalProblemDictionary() -> CFDictionary? {
let key = "key"
let value = "value"
var keyCallBacks = CFDictionaryKeyCallBacks()
var valueCallBacks = CFDictionaryValueCallBacks()
let cfDictionary = CFDictionaryCreate(kCFAllocatorDefault, UnsafeMutablePointer(unsafeAddressOf(key)), UnsafeMutablePointer(unsafeAddressOf(value)), 1, &keyCallBacks, &valueCallBacks)
return cfDictionary
}
Fairly straightforward (and a bit silly) but its a function returning and optional CFDictionary. The "fun" starts when trying to create an NSDictionary from this function:
Why won't the following work?
if let problemDictionary = optionalProblemDictionary() as? NSDictionary {
print(problemDictionary) // never enters, no warnings, compiles just fine
}
While this works fine?
if let cfDictionary = optionalProblemDictionary() {
let problemDictionary = cfDictionary as NSDictionary
print(problemDictionary)
}
XCode 7.0 (7A220)
The reason seems to be that the function returns an optional
CFDictionary? and that can not be cast to a (non-optional)
NSDictionary.
Here is a simpler example demonstrating the same problem with CFString vs NSString:
let cfString = "foobar" as CFString?
if let s1 = cfString as? NSString {
print("s1 = \(s1)") // not executed
}
(The question remains why this does not give a compiler error or
at least a compiler warning because this optional cast can
never succeed.)
But a casting to an optional NSString? works:
if let s2 = cfString as NSString? {
print("s2 = \(s2)") // prints "s2 = foobar"
}
In your case, if you change the "problematic case" to
if let problemDictionary = cfDict as NSDictionary? {
print(problemDictionary)
}
then the if-block is executed.
Note that your method to build a CFDictionary in Swift is not correct
and actually caused program crashes in my test. One reason is that
the dictionary callbacks are set to empty structures.
Another problem is that unsafeAddressOf(key) bridges the Swift
string to an NSString which can be deallocated immediately.
I don't know what the best method is to build a CFDictionary in Swift,
but this worked in my test:
func optionalProblemDictionary() -> CFDictionary? {
let key = "key" as NSString
let value = "value" as NSString
var keys = [ unsafeAddressOf(key) ]
var values = [ unsafeAddressOf(value) ]
var keyCallBacks = kCFTypeDictionaryKeyCallBacks
var valueCallBacks = kCFTypeDictionaryValueCallBacks
let cfDictionary = CFDictionaryCreate(kCFAllocatorDefault, &keys, &values, 1, &keyCallBacks, &valueCallBacks)
return cfDictionary
}

Assign Value of NSNumber to AnyObject

I have a segment of code that gets info from an API, and I need to add it to a Dictionary. The code is below:
typealias JSONdic = [String: AnyObject]
var weatherData: AnyObject = StorageManager.getValue(StorageManager.StorageKeys.WeatherData)!
let json: AnyObject = ["Any": "Object"]
if let json = json as? JSONdic, history = json["history"] as? JSONdic, tempi = history["tempi"] as? Int, hum = history["hum"] as? String, precip = history["precipi"] as? String{
println("Temperature:\(tempi) Humidity:\(hum) Precipitation:\(precip)")
weatherData = [NSDate: AnyObject]()
let temp = tempi as NSNumber
weatherData[(The Current Date)] = temp
}
I want to first add "temp" to the weatherData Dictionary, but even after casting it to NSNumber, I am told that an NSNumber value cannot be assigned to the AnyObject?! type. Can anyone help me fix this?
Your weatherData variable is of type AnyObject. Despite the fact that you later assign it a value of type [NSDate: AnyObject], the variable itself is still considered by the compiler to be AnyObject. You then hit problems because you try to subscript it, assigning an NSNumber, which is obviously not possible on AnyObject.
Your declaration of weatherData should ensure it is the type you intend. If you are sure that your StorageManager will return you the appropriate dictionary type for the weather data key, you can force downcast it to the correct type:
var weatherData = StorageManager.getValue(StorageManager.StorageKeys.WeatherData) as! [NSDate: NSObject]

Swift Dictionary [String:String] to NSMutableDictionary?

I am trying to create and assign a Swift Dictionary of type [String : String] at the SKSpriteNode property userData which requires an NSMutableDictionary. When I try this I get the error:
'[String : String]' is not convertible to 'NSMutableDictionary'
Can anyone point me in the right direction?
// USER DATA
var dictionary: [String : String] = Dictionary()
dictionary["Orbit"] = orbit
dictionary["Zone"] = zone
dictionary["Impact"] = impact
var foundationDictionary = dictionary as NSMutableDictionary
neoSprite.userData = foundationDictionary
There’s no built-in cast for this. But instead you can use NSMutableDictionary’s initializer that takes a dictionary:
var foundationDictionary = NSMutableDictionary(dictionary: dictionary)
One alternative answer to the answers above is to cast your dictionary to NSDictionary create a mutable copy of it and cast it to NSMutableDictionary. Too complicated, right ? Therefore I recommend creating new NSMutableDictionary from Dictionary this is just an alternative
var foundationDictionary = (dictionary as NSDictionary).mutableCopy() as! NSMutableDictionary
Can you try to replace this line:
var foundationDictionary = dictionary as NSMutableDictionary
With this code:
var foundationDictionary = NSMutableDictionary(dictionary: dictionary)
Dictionaries
Create immutable dictionary
let dictionary = ["Item 1": "description", "Item 2": "description"]
Create mutable dictionary
var dictionary = ["Item 1": "description", "Item 2": "description"]
Append new pair to dictionary
dictionary["Item 3"] = "description"
I know I am so late in answering this question and one answer is already accepted, but still to help someone.
Sometime converting swift dictionary ([String : String] or [AnyHashable : String] or any other) to Objective-C NSMutableDictionary, if there are some nested objects like array or dictionaries within that will not get mutable as so when updating the values will get error of [NSDictionaryI setObject:forKey:]: unrecognised selector sent to instance
To prevent this error one can convert the object in objective-c class.
In my case I am converting a JSON string into swift dictionary and passing it to objective c class by casting it in NSMutableDictionary as per my core requirement. This gives me an above error when I try to update the NSMutableDictionary in Objective C class.
After surfing and trying much I found that if I try to convert my JSON string in NSMutableDictionary in Objective-C class itself than it worked for me.
So try this: In aVC.swift
let Str: String = nsmanagedobject.value(forKey: "<key>") as? String {
if let mutableDict: NSMutableDictionary = ApplicationData.convertJsonObject(fromJsonString: Str) as? NSMutableDictionary {
......
<Your code to pass NSMutableDictionary to respected Objective-C class>
......
}
ApplicationData.h
+ (NSObject *)convertJsonObjectFromJsonString:(NSString *)jsonString;
ApplicationData.m
+ (NSObject *)convertJsonObjectFromJsonString:(NSString *)jsonString {
NSObject *jsonObject = nil;
if ([jsonString length] > 0) {
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
if (jsonData) {
jsonObject = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
}
}
NSLog(#"jsonObject: %#",jsonObject);
return jsonObject;
}
I hope this will help someone and save some time.