I am developing an ios app using the Facebook SDK to login.
I have set a LogInViewController as the initial View Controller in the Storyboard, from where the user logins using the FB account.
I have another ViewController which is loaded correctly once the user logs in.
In the AppDelegate file I am checking for currentAccessToken and if it is not nil, I am loading directly the second ViewController, because the user is already logged in.
However, the currentAccessToken is always nil if I quit the app and relaunch it. It only works if I press the home button and re-open the app while it's still running in the background.
Here are the details in the code:
AppDelegate.swift
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
self.customNavigationBar()
if (!isIcloudAvailable()) {
self.displayAlertWithTitle("iCloud", message: "iCloud is not available." +
" Please sign into your iCloud account and restart this app")
return true
}
if (FBSDKAccessToken.currentAccessToken() != nil) {
self.instantiateViewController("MapViewController", storyboardIdentifier: "Main")
}
return FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
}
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool {
return FBSDKApplicationDelegate.sharedInstance().application(
application,
openURL: url,
sourceApplication: sourceApplication,
annotation: annotation)
}
func applicationWillResignActive(application: UIApplication) {
FBSDKAppEvents.activateApp()
}
func applicationDidBecomeActive(application: UIApplication) {
FBSDKAppEvents.activateApp()
}
LogInViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
// Listen to the Facebook notification and when received, execute func handleFBSessionStateChangeWithNotification
NSNotificationCenter.defaultCenter().addObserver(self, selector:"handleFBSessionStateChangeWithNotification:", name: "SessionStateChangeNotification", object: nil)
}
func handleFBSessionStateChangeWithNotification(notification: NSNotification) {
// Switch to MapViewController when logged in
if ((FBSDKAccessToken.currentAccessToken()) != nil) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let mapViewController = storyboard.instantiateViewControllerWithIdentifier("MapViewController") as! MapViewController
self.presentViewController(mapViewController, animated: false, completion: nil)
}
}
I don't know if it is related, but I am also getting a warning for the MapViewController because there is no segue put towards it from the Storyboard:
Warning: Attempt to present MapViewController whose view is not in the
window hierarchy!
The problem is because you are calling for FBSDKAccessToken.currentAccessToken() before having called
FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
You can check for the access token anytime after calling the above line.
EDIT: Explanation
The above line lets the Facebook SDK process the launchOptions and extract the necessary information which it will require to recognise and persist the user for the application.
In cases where the user is already logged in, this simply initialises the Facebook SDK which in turn logs in the user on the basis of persisted data.
I spent a nice half-day banging my head against this issue. In spite of making sure all the delegate methods were present, the FBSDKAccessToken.current() always returned nil.
It turns out that this is because Keychain Sharing is not enabled ( Xcode 8, iOS 10). To fix, go to App -> Capabilities -> Keychain Sharing and turn ON.
Once this is done, you have to still go through the authorization process and return back to the app. Everything should be fine after.
If you already tried the upon solutions, but still have the problem, try this.
My co-programmer and I are using the LoginManager().login method provided in Facebook Swift SDK. (Xcode8, Swift 3, iOS 10)
One possible action that caused that issue is when you login successfully and kill the app immediately, then open your app again and the AccessToken.current returns nil. After login, if you wait 20 seconds or longer (we are not sure the exact waiting time, we waited for 7-20 seconds) and kill the app, the problem seemed to be solved.
We are not sure why this is happened, but this solved our problem, and our guess is that there may be a network delay or a bug.
For those who still having the problem even after set the FBSDKApplicationDelegate.sharedInstance() on the appdelegate, turn out if u get error on your Xcode console like this one
Falling back to storing access token in NSUserDefaults because of simulator bug
it is simulator bug and i try using real device and it work, the access token is not nil again..
so maybe answer #victor comment about this one on #Kal answer..
Related
When I kill the app by swiping up in the multi-app UI in the simulator and relaunch it the application (didFinishLaunchingWithOptions) method is not called, and every time just the login screen shows up. I do not understand what's going on and it really defeats the purpose of checking whether user is logged in or not from firebase if the method is not even called while launching the app again, would really appreciate some help! (Does this have something to do with SceneDelegate methods I am seeing, from what i understand the didFinishLaunching method should be called regardless when launching an application)
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
print("---------appDelegate didFinishLaunchingWithOptions called!---------------")
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
window?.rootViewController = MainViewController()
FirebaseApp.configure()
return true
}
Here's the code for the MainViewController as requested
import UIKit
import Firebase
class MainViewController: UIViewController {
var handle: AuthStateDidChangeListenerHandle?
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.async {
self.handle = Auth.auth().addStateDidChangeListener { (auth, user) in
if user == nil {
print("nil user -----------")
self.perform(#selector(self.showHomeController), with: nil, afterDelay: 3)
} else {
print("non nil user --------")
self.perform(#selector(self.showWelcomeController), with: nil, afterDelay: 3)
}
}
}
}
#objc func showWelcomeController () {
present(WelcomeViewController(), animated: true, completion: nil)
}
#objc func showHomeController () {
present(HomeViewController(), animated: true, completion: nil)
}
}
The ---------appDelegate didFinishLaunchingWithOptions called!--------------- is printed only once, when the project is built and opened in simulator
Disclaimer: I'm very new in iOS app development.
The problem is just the way you "relaunch in the sim". If you kill the app and then tap the app's icon in the simulator, you are no longer running in Xcode; you are running independently. So you don't get any debugging any more; no print messages appear in the Xcode console, you don't stop at breakpoints, etc.
The solution is: relaunch by telling Xcode to build and run again, not by tapping the app's icon in the simulator.
The action of swiping up and killing the application breaks the debugger link. So if you relaunch the application by clicking on the app itself in the simulator, you will no longer have a debugger link with Xcode. That is the reason why you don't see the messages being printed anymore.
You have to relaunch from Xcode to get the launch messages printed once again.
If you have other questions please post them separately. It will be easier to track and respond to by everyone.
We are adding Firebase-Deeplinks to our IOS-project, so that the app can be started via deeplink.
The deeplink-feature itself work fine so far, and so does the default app launch routine. But making both startRoutines work side by side gives me some headache.
What I am trying to achieve get's obvious looking at this code snippet.
func application(_:didFinishLaunchingWithOptions:) {
FirebaseApp.configure()
if "deeplink" {
return true
}
defaultAppLaunch() // no deeplink
return true
}
If there is a deeplink one of these appDelegate-functions is called:
func application(:continueUserActivity:restorationHandler:) {
handleDeeplink()
return true
}
func application(:openURL:options:) {
handleDeeplink()
return true
}
So how do I know at application(_:didFinishLaunchingWithOptions:) if I can call defaultAppLaunch()?
I know there is the launchOptions-Argument in but in my case it is always nil, at least when running the app via XCode. And also the Firebase-Documentation says nothing about launchOptions to be set by Firebase-Deeplinks.
Help is highly appreciated.
TL;DR
You can't know that your app was opened using deeplinks through App Delegate DidFinishLaunching.
Explaination:
App delegate did finish launch is always called, regardless if app was opened normally or via deeplinks. so you can't know through app delegate
Instead, you can know that app was opened through deeplinks if the following delegate function is called.
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
if let dynamicLink = DynamicLinks.dynamicLinks().dynamicLink(fromCustomSchemeURL: url) {
// Handle the deep link. For example, show the deep-linked content or
// apply a promotional offer to the user's account.
// ...
return true
}
return false
}
and you should handle the deeplinks functionality in the same function
I'm referencing the Firebase docs in handling dynamic links for iOS:
Firebase docs for receiving dynamic links
Next, in the application:continueUserActivity:restorationHandler:
method, handle links received as Universal Links when the app is
already installed (on iOS 9 and newer):
func application(_ application: UIApplication, continue userActivity: NSUserActivity,
restorationHandler: #escaping ([Any]?) -> Void) -> Bool {
let handled = DynamicLinks.dynamicLinks().handleUniversalLink(userActivity.webpageURL!) { (dynamiclink, error) in
// ...
}
return handled
}
Finally, in the application:openURL:sourceApplication:annotation: (iOS
8 and older) and application:openURL:options: (iOS 9 and up) methods,
handle links received through your app's custom URL scheme. These
methods are called when your app receives a link on iOS 8 and older,
and when your app is opened for the first time after installation on
any version of iOS.
#available(iOS 9.0, *)
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool {
return application(app, open: url,
sourceApplication: options[UIApplicationOpenURLOptionsKey.sourceApplication] as? String,
annotation: "")
}
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
if let dynamicLink = DynamicLinks.dynamicLinks().dynamicLink(fromCustomSchemeURL: url) {
// Handle the deep link. For example, show the deep-linked content or
// apply a promotional offer to the user's account.
// ...
return true
}
return false
}
But you did mention that the app is currently only being run on Xcode (and I'm guessing iOS Simulator, maybe you can try it on a test device too!)
I have an iOS app written in swift that uses Facebook login (FBSDK 4.11.0). The app is in production, and FB-login works perfectly.
However, while working on a new update I realised that I'm no longer able to sign in or register using FB. The login process stops after having authorized the application in the popup webview. It loads a blank page and stands still.
below code is the exact same code that is currently running perfectly on the version in the app store. I thought this might have something to do with the fact that the app is currently in dev, but after creating a beta and downloading it through testflight I still get the same problem.
Here is my code.
func fbLoginInitiate() {
if hasSeenPermissionPopup(LoginType.Facebook){
Globals.mixpanel.track("Initiated FB login")
FBSDKLoginManager().logInWithReadPermissions(["public_profile", "email", "user_birthday"], fromViewController: self, handler: {(result:FBSDKLoginManagerLoginResult!, error:NSError!) -> Void in
if (error != nil) {
// Process error
self.removeFbData()
} else if result.isCancelled {
// User Cancellation
self.removeFbData()
} else {
//Success
if result.grantedPermissions.contains("email") && result.grantedPermissions.contains("public_profile") && result.grantedPermissions.contains("user_birthday") {
//Do work
self.fetchFacebookProfile()
} else {
Globals.mixpanel.track("Facebook login failed")
SweetAlert().showAlert("Ooops!", subTitle: "Noe gikk galt. Ta kontakt med Mojob dersom feilen vedvarer", style: AlertStyle.Warning)
}
}
})
}
}
Error is nil in the callback, and result only shows if I cancel the request. I've not done any changes to info.plist or any other settings that has to do with FB in the app.
If anyone has experienced anything similar, I would really appreciate hearing about how you solved the problem.
First part of the login works as expected
Second part freezes
Is your AppDelegate correctly configured for the facebook SDK? If not you should add the following functions to it:
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool {
return FBSDKApplicationDelegate.sharedInstance().application(application, openURL: url, sourceApplication: sourceApplication, annotation: annotation)
}
application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
}
Se this issue for more information:
https://github.com/facebook/react-native-fbsdk/issues/72
In the issue they refer to this website where I found the correct setup:
https://developers.facebook.com/docs/ios/getting-started#delegate
I followed the tutorial by google on https://firebase.google.com/docs/notifications/ios/console-topics#receive_and_handle_topic_messages to subscribe to a Firebase topic on my iOS app.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
FIRMessaging.messaging().subscribeToTopic("/topics/Notifications")
let homeViewController = UINavigationController(rootViewController: HomeViewController())
UINavigationBar.appearance().translucent = false
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window?.rootViewController = homeViewController
window?.makeKeyAndVisible()
return true
}
However, when I send a topic push notification out from the Firebase console. I could not receive any push notifications. But when I send out push notification to user segment from the console, the push is working perfectly. When I check the Xcode console, I am seeing this FIRMessaging error.
2016-05-31 11:11:47.893: <FIRMessaging/WARNING> Cannot subscribe to topic: /topics/Notifications with token: (null)
I've tried to search for this error but have no luck finding anything. I am not sure if this is the problem that is causing my app to not receive any push from topics.
Does anyone have this issue and know how to solve it?
Looks like maybe you're calling subscribeToTopic too early.
First, before you set up any Firebase call, make sure you call
FIRApp.configure()
That will ensure that all Firebase services are properly set up and initialized.
Next, you're going to need to wait just a bit to subscribe to topics. Your client needs to first register your app with both APNs and FCM to ensure that it can receive notifications. That involves a network call, which means you can't subscribe to topics when your app first launches.
Instead, I'd recommend putting that code into your application:didRegisterUserNotificationSettings handler instead. Something like this:
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
NSLog(#"Hooray! I'm registered!");
[[FIRMessaging messaging] subscribeToTopic:#"/topics/cool_users"];
}
Edit: And the Swift version...
func application(application: UIApplication, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings) {
print("Hooray! I'm registered!")
FIRMessaging.messaging().subscribeToTopic("/topics/swift_fans")
}
The accepted solution did not work for me. The token is not always available when application:didRegisterUserNotificationSettings: is called.
For example if application is freshly installed and starts for the first time FIRInstanceID.instanceID().token() returns nil.
You need to make sure application calls subscribeToTopic: after the token is available.
I ended up with creating a helper object that enqueues subscribeToTopic:, unsubscribeFrom: calls and executes them in FIFO order after the token arrives.
class FIRMessagingHelper {
private let queue: OperationQueue
init() {
queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
queue.addOperation(TokenReadyOperation())
}
func subscribeTo(topic: String) {
queue.addOperation {
OperationQueue.main.addOperation({
FIRMessaging.messaging().subscribeToTopic(topic)
})
}
}
func unsubscribeFrom(topic: String) {
queue.addOperation {
OperationQueue.main.addOperation({
FIRMessaging.messaging().unsubscribeFromTopic(topic)
})
}
}
}
TokenReadyOperation waits until the token appears. AsynchronousOperation is used as the base class to minimize boilerplate.
class TokenReadyOperation : AsynchronousOperation {
override init() {
super.init()
NotificationCenter.default.addObserver(self,
selector: #selector(TokenReadyOperation.tokenRefreshed(notification:)),
name: .firInstanceIDTokenRefresh,
object: nil)
}
override func didStart() {
finishIfTokenAvailable()
}
private func finishIfTokenAvailable() {
guard FIRInstanceID.instanceID().token() != nil else { return }
markFinished()
}
/// Posted every time token changes
#objc private func tokenRefreshed(notification: Notification) {
finishIfTokenAvailable()
}
}
Few things to keep in mind:
App must call FIRApp.configure() or FIRApp.configureWithOptions(_:) prior making any Firebase calls (as Todd Kerpelman mentioned)
subscribeToTopic:, unsubscribeFrom: are not thread safe and must be executed on main thread
Topic names has to be in "/topics/*" format (as henmer mentioned)
Make sure to use different configuration plist for debug and App Store release of your app. See FIRApp.configureWithOptions(_:) documentation.
Date & Time should be current, otherwise the token may not be delivered.
Make sure to use the newest framework version. I had issues with notification delivery with the SDK released around January 2017.
My problem was not solved by calling subscribeToTopic after
func application(application: UIApplication, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings) {
instead it worked by calling subscribeToTopic after
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
this function get called when you get your push token from APNS not firebase.
Xcode 8.3.2
Swift 3.0
I am using SVProgressHUD in my swift project as a framework. It is working fine in iOS 8. I have login with Facebook in my app. whenever i use the login SVProgressHUD doesn't show through out the app. But when i killed the app and launch SVProgressHUD will work fine. I already added required keys in info.plist. I am unable to figure out the problem. Please anyone give the solution.
Edit: if i change "loginbehaviour" to Web and SystemAccount. it is working fine.
Environment
FBSDK :- 4.8.0
XCode :- 6.4, Swift 1.2
LoginViewController.swift
func loginButton(loginButton: FBSDKLoginButton!,
didCompleteWithResult
result: FBSDKLoginManagerLoginResult!,
error: NSError!) {
SVProgressHUD.showWithStatus(NSLocalizedString("LOGIN_PROGRESS_MSG", comment: ""))
if (error != nil) {
DDLogError("FB Login error: \(error.localizedDescription)")
SVProgressHUD.showErrorWithStatus(NSLocalizedString(error.localizedDescription, comment: ""))
} else if result.isCancelled {
DDLogError("FB Login cancelled")
SVProgressHUD.showErrorWithStatus(NSLocalizedString("Login cancelled", comment: ""))
} else {
DDLogDebug("FB Login success")
SVProgressHUD.showWithStatus(NSLocalizedString("FB_USER_FETCH_MSG", comment: ""))
returnUserData()
}
}
Appdelegate.swift
let fbLaunch = FBSDKApplicationDelegate.sharedInstance().application(application,
didFinishLaunchingWithOptions: launchOptions) // Added this method in didFinishLaunchingWithOptions method
also implemented
func application(application: UIApplication,
openURL url: NSURL,
sourceApplication: String?,
annotation: AnyObject?) -> Bool {
return FBSDKApplicationDelegate.sharedInstance().application(
application,
openURL: url,
sourceApplication: sourceApplication,
annotation: annotation)
}
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
LocationManager.sharedInstance.startLocationManger()
FBSDKAppEvents.activateApp()
//Send device token change
APNSManager.validateAndUpdateAPNSToken()
//Look for APNS payload in background if we have saved any
APNSManager.handlePendningAPNSPayloads()
}
Svprogresshud is not showing in case of a heavy task is performing on main thread.showing loader after a delay of 4 ms will solve the problem
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(400)) {
SVProgressHUD.show()
}
try showing SVProgressHUD in main queue when calling from block
dispatch_async(dispatch_get_main_queue(), {
SVProgressHUD.showWithStatus(NSLocalizedString("LOGIN_PROGRESS_MSG", comment: ""))
})