I'm currently developing an iOS application that integrates Facebook and I'm having a bit of a problem while investigating this with Swift (with ObjC I have no problems).
The thing is, this is the method that gets executed in the appDelegate when coming from another APP (in this case FB in a WebBrowser):
func application(
application: UIApplication,
openURL url: NSURL,
sourceApplication: NSString,
annotation: AnyObject)
-> Bool {
let appString : String = sourceApplication as String // Try to convert format => EXCEPTION
let appString : String = String(sourceApplication) // 'SSS' Suggestion: EXCEPTION
println(sourceApplication) // Try to print the value => EXCEPTION
return FBAppCall.handleOpenURL(url, sourceApplication:sourceApplication,
withSession:session) // With Parse => EXCEPTION
}
And inside that method I'm having real trouble with the 'sourceApplication' parameter. I try to use it, I get an exception. I try to convert it, another exception...can't even log its value because it crashes when accessing its value. Changing the parameter type in the functions signature to String neither worked.
This is the error I'm getting:
EXEC_BAD_ACCESS
And I've been able to track down until I could read this that it's definitely a valuable hint:
ObjectiveC.NSString.__conversion (ObjectiveC.NSString)() -> Swift.String
Could it be an iOS8 bug?
Any of you has had this problem and/or knows how to solve it?
You have made two mistakes:
The function declaration from the app Delegate is func application(application: UIApplication!, openURL url: NSURL!, sourceApplication: String!, annotation: AnyObject!) -> Bool
: sourceApplication is an optional String value not NSString.
Since sourceApplication is an optional it may return nil value (In your case returning nil) . Type casting nil to String is not safe , therefore it is crashing.
Solutions :
- No type casting is required in your case Since returned value is String type
- Use optional form type cast operator as? to safely type cast i.e
if let appString = sourceApplication {
println(appString as? String)
}
This is working for me (with FacebookSDK):
func application(application: UIApplication, openURL url: NSURL, sourceApplication: NSString?, annotation: AnyObject) -> Bool {
var wasHandled:Bool = FBAppCall.handleOpenURL(url, sourceApplication: sourceApplication)
return wasHandled
}
I don't get this in the playground. Could be a iOS 8 bug as you suggest
But for the sake of trying, can you try
let appString : String = String(sourceApplication)
For FB Messenger here's what I did to get some better handling in my AppDelegate. Most of the ideas were taken straight from the FB IOS documentation and ported to Swift.
Why did I feel like I should write an additional reply? I've got some experience with Swift, but felt like I wasted enough time trying to get the right set of code to do what I wanted with FB Messenger. Hopefully the raw code is useful to someone, just to consolidate a lot of bits and pieces and save some time.
NOTE: This does not include all the AppDelegate lifecycle methods you'll want/need, but hopefully it's a head start
@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, FBSDKMessengerURLHandlerDelegate {
var window: UIWindow?
var messengerUrlHandler: FBSDKMessengerURLHandler?
var cancelContext : FBSDKMessengerURLHandlerCancelContext?
var composerContext : FBSDKMessengerURLHandlerOpenFromComposerContext?
var replyContext: FBSDKMessengerURLHandlerReplyContext?
// Facebook Messenger
enum MessengerShareMode : Int {
case MessengerShareModeCancel
case MessengerShareModeSend
case MessengerShareModeComposer
case MessengerShareModeReply
}
// shareMode holds state indicating which flow the user is in.
// Return the corresponding FBSDKMessengerContext based on that state.
var shareMode : MessengerShareMode?
/*
* Initialize the FB messenger handler and set self as the delegate.
*/
func application(application: UIApplication, willFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
YARAppearance.setAppearance()
let rootController = TabBarController()
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window!.rootViewController = rootController
self.window!.makeKeyAndVisible()
// Facebook messenger handling
self.messengerUrlHandler = FBSDKMessengerURLHandler()
if (self.messengerUrlHandler != nil) {
self.messengerUrlHandler!.delegate = self
}
return true
}
/*
* Handle the cancel context flow.
*/
func messengerURLHandler(messengerURLHandler: FBSDKMessengerURLHandler!,
didHandleCancelWithContext context: FBSDKMessengerURLHandlerCancelContext!) {
self.cancelContext = context
self.shareMode = .MessengerShareModeCancel
}
/*
* When people enter your app through the composer in Messenger,
* this delegate function will be called.
*/
func messengerURLHandler(messengerURLHandler: FBSDKMessengerURLHandler!,
didHandleOpenFromComposerWithContext context: FBSDKMessengerURLHandlerOpenFromComposerContext!) {
self.composerContext = context
self.shareMode = .MessengerShareModeComposer
}
/*
* When people enter your app through the "Reply" button on content
* this delegate function will be called.
*/
func messengerURLHandler(messengerURLHandler: FBSDKMessengerURLHandler!,
didHandleReplyWithContext context: FBSDKMessengerURLHandlerReplyContext!) {
self.replyContext = context
self.shareMode = .MessengerShareModeReply
}
/*
* Handle URL calls from external applications, particularly Messenger
*/
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool {
let wasHandled:Bool = self.messengerUrlHandler!.openURL(url, sourceApplication: sourceApplication)
return wasHandled
}
/*
* A way to access the context objects elsewhere
*/
func getContextForShareMode() -> FBSDKMessengerContext? {
// shareMode holds state indicating which flow the user is in.
// Return the corresponding FBSDKMessengerContext based on that state.
if (shareMode == .MessengerShareModeSend) {
// Force a send flow by returning a broadcast context.
return FBSDKMessengerBroadcastContext()
} else if (shareMode == .MessengerShareModeComposer) {
// Force the composer flow by returning the composer context.
return self.composerContext!
} else if (shareMode == .MessengerShareModeReply) {
// Force the reply flow by returning the reply context.
return self.replyContext!
}
return nil
}
}