Completion handler as var in Swift from Objective-C - objective-c

I must present this completion handler in Swift as variable:
-test:(void(^)(id result, NSError* error))completion;
What must this look like in Swift? Is in this case id == AnyObject and NSError == Error?

id is imported into Swift as Any, NSError * becomes Error? in Swift 3 so your completion handler should read like this in Swift:
func completionHandler(result: Any, error: Error?) {
// Do something
}
obj.test(completionHandler)
Or if you want to define it inline:
let handler = { (result: Any, error: Error?) -> Void in
// Do something
}
obj.test(handler)

Related

EXC_BAD_ACCESS when passing a NULL id to a callback from Obj-C to Swift

I'm trying to debug a problem I'm encountering in a 3rd party library and have reached a brick wall. The issue is that I am calling the library in Swift, and passing it a callback. The library then executes Obj-C code which ends up calling the callback with NULL, which inexplicably crashes with EXC_BAD_ACCESS (code=1, address=0x0)
Swift code:
return AWSMobileClient.sharedInstance().interceptApplication(app, didFinishLaunchingWithOptions: opts) { (res: Any?, err: Error?) in
guard err == nil else {
Log.e("Error registering with AWS: \(err!.localizedDescription)")
return
}
Log.i("No error registering with AWS")
}
Obj-C code (simplified):
- (BOOL)interceptApplication:(UIApplication *)application
didFinishLaunchingWithOptions:(nullable NSDictionary *)launchOptions
resumeSessionWithCompletionHandler:(void (^)(id result, NSError *error))completionHandler {
completionHandler(NULL, NULL); // Crashes here if 1st argument is NULL
completionHandler(#"hello", NULL); // Doesn't crash
return YES;
}
As you can see, I have properly marked the Swift callback to have type Any?, so it should accept NULL/nil no problem.
If anyone can explain why this is happening and how to fix it that would be fantastic.

Call Swift function with completion handler in objective c

I am trying to call a Swift function that contains a completion handler in an objective C class, but I am not sure how to implement it.
This is my Swift Code
#objc class textToSpeech:NSObject{
func toSpeech(word: NSString, sucess:()->Void) -> NSURL {
let tempDirectory = NSURL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
let tempFile = tempDirectory.URLByAppendingPathComponent((word as String) + ".wav")
let tts = TextToSpeech(username: "xxxxxx", password: "xxxxxx")
tts.synthesize(word as String,
voice: SynthesisVoice.GB_Kate,
audioFormat: AudioFormat.WAV,
failure: { error in
print("error was generated \(error)")
}) { data in
data.writeToURL(tempFile, atomically: true)
print("createdURL")
print(tempFile)
sucess();
}
return tempFile
}
How would I write the function call in objective c. I have already completed setting up the project so that I can call swift functions from objective c.
For example you have this code:
#objc class PDTextToSpeech: NSObject{
func toSpeech(word: NSString, success: () -> Void) -> NSURL {
// ...
return NSURL()
}
}
So you could easily bridge you Swift code in obj-c with #import "<ModuleName>-Swift.h"
where you project name.
Then you can call:
[[PDTextToSpeech new] toSpeech:#"String" success:^{
NSLog(#"Success");
}];
I was using PDTextToSpeech as class name, because it's preferable to call classes in obj-c with uniq prefix. If you project called TestProject - you can use TP prefix.
I guess it should look like this:
textToSpeech* text = [[textToSpeech alloc] init];
[text word:#"some text" sucess:^{
NSLog(#"success");
}];

Apple watch communicate with iOS app a new swift project vs to existing obj c project

1) I've managed to communicate from apple watch to iOS app using a complete swift project (iOS app in swift and Apple Watch target in swift)
Code:
In InterfaceController.swift
#IBAction func buttonPressed() {
let watchMessage = ["SiteName" : "Tech-recipes"]
WKInterfaceController.openParentApplication(watchMessage, reply: { (reply:[NSObject : AnyObject]!, error: NSError!) -> Void in
if let message = reply["Message"] as? String{
println(message)
}
})
}
in AppDelegate.swift
func application(application: UIApplication!, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]!, reply: (([NSObject : AnyObject]!) -> Void)!) {
if let siteName = userInfo["SiteName"] as? String{
reply(["Message":"Hello from \(siteName)"])
}
}
Output: "Hello from Tech-recipes"
2) However, when I want to integrate the apple watch target in swift to the exiting project in obj-c, it will crash and give me this error: "fatal error: unexpectedly found nil while unwrapping an Optional value"
In InterfaceController.swift
#IBAction func buttonPressed() {
let watchMessage = ["SiteName" : "Tech-recipes"]
WKInterfaceController.openParentApplication(watchMessage, reply: { (reply:[NSObject : AnyObject]!, error: NSError!) -> Void in
if let message = reply["Message"] as? String{ //CRASH HERE!!!!
println(message)
}
})
}
iPhoneAppDelegate.m
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply{
// Performs task on phone here
// Sending back a reply
if ([[userInfo valueForKey:#"SiteName"] length]>0) {
//NSMutableDictionary *reply = [[NSMutableDictionary alloc] init];
[reply setValue:[NSString stringWithFormat:#"Hello from %#", [userInfo valueForKey:#"SiteName"]] forKey:#"Message"];
}
}
Update:
--> Noticed that in handleWatchKitExtensionRequest: method the type for userInfo is different in swift and obj-c, in [NSObject : AnyObject]! and NSDictionary respectively. How to solve this?
--> Got an error in error: NSError!: [0] (null) #"NSLocalizedDescription" : #"The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]"
Solved it!
Missed out the reply(aNsdictionary) in the app delegate

Type Error in closure (translated code from obj c to swift)

I am trying to use a library which is written in objective c in my swift application.
I tried to translate a snippet from the readme to swift code - But I get a type error I don't understand.
Obj.C code from readme:
[self.client logonWithUsername:self.username.text password:self.password.text responseCallback:^(NSDictionary *response, NSError *error) {
if (error) {
[self handleFailedAuth:error];
return;
}
[self handleSuccessfulAuth];
}];
My translation to swift:
var username = NSUserDefaults.standardUserDefaults().stringForKey("username")
var password = NSUserDefaults.standardUserDefaults().stringForKey("password")
client.logonWithUsername(username, password: password, responseCallback: {
(response: NSDictionary, error: NSError) in
if(error){
handleFailedAuth(error)
return;
}
handleSuccessfulAuth()
}
)
I Get [NSObject: AnyObject]! is not a subtype of NSDictionary on the line where the parameters of the closure are defined. How is that possible? I am using the same types as in the example.
Your Swift should probably read the following:
var username = NSUserDefaults.standardUserDefaults().stringForKey("username")
var password = NSUserDefaults.standardUserDefaults().stringForKey("password")
client.logonWithUsername(username, password: password, responseCallback: {
(response: NSDictionary?, error: NSError?) in
if(error){
handleFailedAuth(error!)
return;
}
handleSuccessfulAuth()
}
)
This is because Swift optionals in some ways replace the way you used to pass nil in objective-c. So because the NSDictionary might be nil and the NSError might be nil, you put a ?-mark after them, then conditionally unwrap w/ a !-mark inside the block when you need to access the value of that
You're explicitly specifying the block/closure parameter types in Swift, and the Swift compiler does not have enough information about the NSDictionary. This is because the Swift Dictionary is more strongly typed than the Objective-C NSDictionary.
The error message says (admittedly, pretty cryptically) that the exact type Swift is expecting is a Dictionary<NSObject, AnyObject>!.
There are a couple of ways to solve this. One is to be more explicit about your NSDictionary parameter in the Swift closure definition:-
client.logonWithUsername(username, password: password, responseCallback: {
(response: Dictionary<NSObject, AnyObject>!, error: NSError) in
// ... handler body
}
)
A somewhat easier way is to not try to tell Swift about the types at all and let the compiler infer what it needs:-
client.logonWithUsername(username, password: password, responseCallback: {
response, error in
// ... handler body
}
)

Completion Blocks Syntax in Swift

Slowly getting into Swift but still struggling with the completion blocks. How would the following code look like in Swift?
[self.eventStore requestAccessToEntityType:type completion:^(BOOL granted, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
[self alertViewWithDataClass:((type == EKEntityTypeEvent) ? Calendars : Reminders) status:(granted) ? NSLocalizedString(#"GRANTED", #"") : NSLocalizedString(#"DENIED", #"")];
});
}];
self.eventStore.requestAccessToEntityType(type) {
(granted: Bool, err: NSError!) in
dispatch_async(dispatch_get_main_queue()) {
...
}
}
for an example of working code, I was experimenting with this exact API in swift :)
Your Objective-C 'completion blocks' in Swift (now called 'closures' in this context) will contain all of the same information:
parameter labels and types (at the start of the block in parentheses)
return type (preceded by '->')
the keyword 'in' separating the signature from the code
Note that the signature of the method specifies the type for the parameters, so all you really need to do there is supply names for them :) (type inference FTW!) Additionally, your block returns 'Void' so we don't need to include the return type here, either.
That would give us:
self.eventStore.requestAccessToEntityType(type) { (granted, err) in
dispatch_async(dispatch_get_main_queue()) {
...other stuff...
}
}