Reading NSNotification userInfo in Swift - ios

(I'm new to Swift and iOS development in general).
I'm porting some Objective-C code over to Swift and I don't know how to translate this:
-(void) fooDidBar:(NSNotification *)notification
{
Foo* foo = [[notification userInfo] objectForKey:BarKey];
// do stuff with foo
}
So far I have this:
private func fooDidBar(notification: NSNotification) {
var foo: Foo = notification.userInfo.objectForKey(BarKey)
}
But I get a compiler error:
/Users/me/src-me/project/FooBarViewController.swift:53:57: Value of type '[NSObject : AnyObject]?' has no member 'objectForKey'
As userInfo is declared as an NSDictionary in UIApplicationShortcutItem.h:
#property (nullable, nonatomic, copy) NSDictionary<NSString *, id <NSSecureCoding>> *userInfo;
...I thought I'd try this:
= notification.userInfo[BarKey]
but then I get this error:
/Users/me/src-me/project/FooBarViewController.swift:53:65: Type '[NSObject : AnyObject]?' has no subscript members

Your idea to use subscripts was correct, however as you can see by the presence of ?, the userInfo dictionary is Optional, meaning that it can be nil (in Objective-C, no distinction is made). Furthermore, Swift's Dictionary is not as lenient with types as NSDictionary, so you'll need to use as? to ensure the value is a Foo instance as you expect.
You can't directly subscript the optional dictionary, but if you use a ? for optional chaining, then you can access the value if the dictionary is non-nil. I would also recommend if let to access the final value if it's non-nil. (Or you might choose to use guard let with a fatalError or return in case the Foo is not present.)
if let foo = notification.userInfo?[BarKey] as? Foo {
// foo is non-nil (type `Foo`) in here
}

Related

How to pass a Dictionary to Objective-C method in Swift

I need to pass a Dictionary to a Objective-C method in Swift. In Swift the code is like this:
let modelData: Dictionary<String, [Double]> = getModelData()
result = myChilitags.estimate3D(configAt: configFilePath, forModel: modelData);
(The configure file has nothing to do with this problem, just ignore it.)
I used a .h file:
#interface myChilitags : NSObject
+ (nonnull UIImage *)estimate3D:configAt:(NSString *)configFilePath forModel:(nonnull NSDictionary*) modelData;
#end
The question is that I need to do something with the modelData in the Objective-C method estimate3D but I don't know what to do after I passed the Dictionary value modelData to the method.
I tried to just print the modelData value but all that came out was:
1
I also tried to print the value in the Dictionary like:
std::cout << modelData["face001"] << std::endl;
I am pretty sure that there is a key "face001" in the dictionary but the result was still:
1
I know it must have something to do with NSDictionary and Dictionary but I just don't know what to do.
First of all, a Dictionary in Swift is struct, an NSDictionary is class.
Objective-C is not type-safe so it doesn't show an error.
If you try to do the same in Swift, it will tell you
Cannot assign value of type '[String : [Double]]' to type 'NSDictionary'
let swiftDictionary = [String: [Double]]()
var nsDictionary = NSDictionary()
nsDictionary = swiftDictionary //shows error
So you have to convert the Swift dictionary to an NSDictionary.
let modelData: Dictionary<String, [Double]> = getModelData()
let nsModelData = NSDictionary(dictionary: modelData)
result = myChilitags.estimate3D(configAt: configFilePath, forModel: nsModelData);

Swift compile time errors when using generics and accessing dictionary's values

Below I have a class, B, with a generic type and the generic type has a subclass type constraint. In a separate class, A, I create a dictionary property, values, with the key as String and value as B. Class A then has methods to return or set values in the dictionary such that the values are not constrained to a single type (they maintain their generic SomeType type which is a subclass of NSObject). However, this produces the following two errors noted inline below:
class A: NSObject {
var values = [String : B]()
func get<SomeType: NSObject>(key: String) -> B<SomeType>? {
// #1 Error on line below: cannot convert return expression of type 'B<NSObject>?' to return type 'B<SomeType>?'
return values[key]
}
func set<SomeType: NSObject>(key: String, value: B<SomeType>) {
// #2 Error on line below: cannot assign value of type 'B<SomeType>' to type 'B<NSObject>?'
values[key] = value
}
}
class B<SomeType: NSObject>: NSObject {
}
I've attempted various forms of declaring the values dictionary to tell the compiler that SomeType is a subclass of NSObject and everything is going to be ok, but have been unsuccessful. Similarly to this question, I'm a bit stumped because the methods define SomeType as a subclass of NSObject and therefore things appear to be type safe when setting and getting from values.
I could remove the generic types from the methods and instead force the type to be <NSObject>, but then I'd run into the same problem as noted here.
This may not be doing what you think it's doing:
var values = [String : B]()
This isn't really [String : B], it's [String : B<NSObject>]. (I'm actually kind of surprised that this is legal syntax; I'd be tempted to open a bugreport about that; B isn't a proper type in Swift.) You may realize this already, but it's the first important note.
The second important note is that generic types are not covariant in Swift. A Thing<Cat> is not a subtype of Thing<Animal>. There are some type-theory reasons for this, and there are some technical implementation reasons for this, but the important fact is that it's not possible in Swift.
If you need to hold a variety of B types, then you'll need to build a type eraser. In your case, the type eraser could possibly be B<NSObject> with something like this:
class B<SomeType: NSObject>: NSObject {
let value: SomeType
init(value: SomeType) {
self.value = value
}
func liftGeneric() -> B<NSObject> {
return B<NSObject>(value: value)
}
}
If you need to hold just one kind of B type, then make A generic as well.

How to convert NSDictionary to NSObject in Swift?

I have a meta variable NSObject in a viewcontroller that I intend to be able to receive any kind of object from the parent viewcontroller that pushes it. I also have a type variable that will determine how I typecast and interpret this NSObject inside the viewcontroller.
The problem is when I tried to cast an NSDictionary into NSObject in the parent, Xcode warns that that type of typecasting will always fail.
Code that I have tried:
childVc.meta = ["title":"test"] as! NSObject; // warning: cast from '[String:String?]' to unrelated type 'NSObject' always fails.
let data = ["title":"test"];
childVc.meta = data as! NSObject; // warning: cast from '[String:String?]' to unrelated type 'NSObject' always fails.
let data = ["title":"test"];
childVc.meta = data as NSObject; // error: cannot convert value of type '[String:String?]' to type 'NSObject' in coercion.
let data = ["title":"test"] as! NSObject; // warning: cast from '[String:String?]' to unrelated type 'NSObject' always fails.
childVc.meta = data;
But the opposite typecasting always works:
let unwrappedMeta = self.meta as! NSDictionary;
Oh btw, I know that swift doesn't need semicolon at the end. It's just my habit from obj-c and swift looks like doesn't care about it, so don't let this semicolon distract us. :)
You have an optional value in dictionary, seems not to be NSDictionary.
// warning: cast from '[String:String?]'
Try to cast it as AnyObject
Please try to use like this:
var traitsDic : NSDictionary! = ["title":"test"]
var traits = traitsDic as Dictionary<String, AnyObject>

Swift - extract data from dictionary

I have custom object, that contains json. If I print it, it looks like
{
"first_name" = Name;
id = 111111;
"last_name" = LastName;
"photo_100" = "https://url.jpg";
}
This object has property .json. To extract data from it in objective-C I use
NSLog(#"id%#", [[response.json firstObject] objectForKey:#"id"]);
But in Swift, if I try
var dict = response.json
self.name = dict.firstObject.objectForKey("first_name")
self.lastName = dict.firstObject.objectForKey("last_name")
self.photoUrl = dict.firstObject.objectForKey("photo_100")
I get compile or runtime error. I tried call firstObject in dict declaration and and tried downcasting to string - everything leads to errors.
How to extract data correctly ?
UPD: object definition
#interface VKResponse : VKObject
/// Request which caused response
#property(nonatomic, weak) VKRequest *request;
/// Json content of response. Can be array or object.
#property(nonatomic, strong) id json;
/// Model parsed from response
#property(nonatomic, strong) id parsedModel;
/// Original response string from server
#property(nonatomic, copy) NSString *responseString;
#end
If you can write [response.json firstObject] in Objective C, then response.json is not a dictionary, but an array.
In your Swift code you cast it to a dictionary, which clearly won't work.
Based on the code you posted your custom object doesn't contain JSON, it contains objects that were created from JSON. As #TheEye pointed out in their answer, your objective C code suggests that what you have is an array of dictionaries.
Swift is more strictly typed than Objective-C.
By default dictionaries and arrays are homogeneous: Dictionaries can only contain key/value pairs where the type of the key is always the same and the type of the value is always the same.
Likewise Swift Arrays are normally typed so that they must contain all the same type of object.
You can create Swift Dictionaries or Arrays that contain generic objects ([AnyObject: AnyObject] for a dictionary or [Anyobject] for an array).
Post the definition of your custom object and it's json property. We need to know how it's declared in order to figure out exactly what you need to do to fix your problem.
First make sure what kind of object you have.
if let jsonDict = json as? [String:AnyObject] {
}
if let jsonArray = json as? [AnyObject]{
}

Read NSArray of CGPoints in Swift

I have a function in an Objective-C class that returns an NSArray:
- (NSArray *) myAwesomeFunction {
// Good stuff here
return array
}
Now I know that the members of this array are all NSValue objects with a CGPoint inside. This information is not available to Swift however, which infers the return type (correctly) as [AnyObject]!
Now when I try to cast the members of the array into an NSValue I get a cast error as I should.
let myArray = AnObjectiveCClass.myAwesomeFunction()
let myValue = myArray[0] as NSValue // Cast error since AnyObject cannot be cast to NSValue
This leaves me with a problem though, how can I access the contents of the array in my Swift class? In the end I want to retrieve the CGPoint values inside of the array.
In my opinion, you can cast the the whole array to a [NSValue] array:
if let downcastNSValueArray = myArray as? [NSValue] {
let myValue = downcastNSValueArray[0]
}
You can simply add an Objective-C generics annotation to your method:
+ (NSArray<NSValue *> *) myAwesomeMethod {
// Good stuff here
return array;
}
Now Swift automatically infers the type:
let myArray = MyClass.myAwesomeMethod()
let myValue = myArray[0]
let myPoint = myValue.pointValue
Note that myArray will be an implicitly-unwrapped optional i.e. an [NSValue]!, so you could also add nullability annotations if you know myAwesomeMethod won't ever return nil. myValue will be considered non-optional. So the code above doesn't need any additional type handling to satisfy the compiler, but may still trigger runtime exceptions if myArray == nil or if myArray.count < 1.

Resources