IOS Facebook SDK 4.01: Should I be using FBSDKAccessToken.currentAccessToken() to check if user is logged in? - swift

I getting trying to get familiar with FB's newest IOS SDK (4.0.1). I've integrated it into an IOS8 Swift project and using the FBSDKLoginButton to log users in and out. I want the application to skip showing the Login view controller if the user has already logged in.
Should I just be checking the return result of FBSDKAccessToken currentAccessToken()? Does this return 'nil' if a user is not logged in? The docs have the following to say about this method:
You can load this with the SDK from a keychain cache or from an app
bookmark when your app cold launches. You should check its
availability in your view controller's viewDidLoad.
Something like:
// LoginViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
if(FBSDKAccessToken.currentAccessToken()){
//They are logged in so show another view
}else{
//They need to log in
}
}
So this sounds like it might be what I'm looking for but I can't really tell from this description. How are the good people of SO handling this common use case? :)
-Nick

Yes, FBSDKAccessToken.currentAccessToken() is what you should be checking although the code you have won't work as this does not return a Boolean. You'll need something like:
if(FBSDKAccessToken.currentAccessToken() != nil) {
//They are logged in so show another view
} else {
//They need to log in
}
Also, this alone only works for when the app moves between foreground/background (user hits home button). If the app is killed and cold launched the token will be lost unless you implement the following in didFinishlaunchingWithOptions in the AppDelegate:
return FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
You do not need to worry about the keychain as this is handled for you automatically provided you implement the necessary parts.
The following website has a good tutorial of getting a simple example working: http://www.brianjcoleman.com/tutorial-how-to-use-login-in-facebook-sdk-4-0-for-swift/

Related

How do you detect if an AppKit app has been launched with a document?

I am building an NSDocument-based app and I want to be able to detect if my app is being launched because a user is opening a document, or because the user simply clicked on the dock icon. I tried inspecting the Notification that comes with the applicationDidFinishLaunching method, but it seems not to contain a reference to the document. I also tried querying NSDocumentController.shared.documents from this method, but the array is still empty at this point. I have noticed that by the time applicationDidBecomeActive is called, the document has been instantiated, but of course, this method is also called on occasions not related to app launch, so that's not ideal. My best guess at the moment is to do the following, but it feels like a hack.
class AppDelegate: NSObject, NSApplicationDelegate {
private var hasBecomeActiveOnce = false
func applicationDidBecomeActive(_ notification: Notification) {
if !hasBecomeActiveOnce {
hasBecomeActiveOnce = true
if !NSDocumentController.shared.documents.isEmpty {
print("App launched from document!")
}
}
}
}
There must be a better (more idiomatic) way. What am I missing?
Okay, I think I've found the answer to my specific problem, which might not be fully communicated by the question above. Basically, what I want to do is show an open panel when the app is launched, or when the dock icon is clicked and there isn't a document already open. It seems like in both of these cases, the system calls applicationOpenUntitledFile. Answering my own question in case others are running into this same issue.
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationOpenUntitledFile(_ sender: NSApplication) -> Bool {
print("app launched (or dock clicked) without a document!")
return true
}
}

How to get back to the current window from AppDelegate

In my macOS application, I am following a OAuth-Login procedure.
I am authenticating successfully by receiving a code within a url through my custom url, with which I already can get hold of an access_token.
I start the login procedure with the simple click of a button in a ViewController.
The system I log in to then answers with my registered custom url scheme, providing me with a code I have to then use to get an access_token with POST request. This access_token than can be used to make api calls.
My Problem now is that lacking knowledge of how to implement other techniques, I am currently doing all the latter procedure within the AppDelegate function application(_ application: NSApplication, open urls: [URL]).
From my limited understanding this is the way to handle this, but now
how can I get back from there to get hold of the current view? I am really struggling with the view controller life cycle part of things here I guess...
In AppDelegate, you can get a reference to the current window's ViewController as follows. Replace "MainViewController" with the name of the one you use.
iOS Swift:
if let vc = window?.rootViewController as? MainViewController {
// use `vc` here by calling public functions
// and accessing public properties
}
macOS Swift:
if let vc = NSApplication.shared.mainWindow?.contentViewController as? MainViewController {
// use `vc` here by calling public functions
// and accessing public properties
}
OK, found it: since there is no rootViewController in macOS-land as there is with iOS, it works a little different:
in macOS you can get hold of the window currently "under keyboard" like so:
in application(_:open:), where the redirect_uri gets called:
if let viewController = NSApplication.shared.keyWindow?.contentViewController as? ViewController {
// make changes to the view
}

How to programmatically show a window/view controller in Swift 4 for macOS application

I'm trying to programmatically show a window in my macOS application. The reason I want to do it programmatically is because the user clicks a Login button and the resulting function depends on the success of the login. If it was successful, the window is shown; otherwise, it is not.
Here is a screenshot of my Xcode window:
Here's what I'm trying in the code (Alert is a class I created to make showing NSAlert dialogs easier):
#IBAction func btnLogin_Click(_ sender: Any) {
let email = txtEmail.stringValue
if(email.isEmpty) {
Alert.show(parent: view.window!, message: "Email is required.")
return
}
let password = txtPassword.stringValue
if(password.isEmpty) {
Alert.show(parent: view.window!, message: "Password is required.")
return
}
// this does not work, even though I gave the window and view controllers
// both identifiers in the storyboard
self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("wcStores"))
}
Apple's documentation for NSStoryboard.SceneIdentifier is pretty much nonexistent, and other methods I've seen seem to pertain to earlier versions of Swift.
What am I doing wrong?
Alright, so this is not what I originally was trying to do, but this way is much better, and what I was really looking for.
let vcStores = self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("vcStores"))
as! NSViewController
self.view.window?.contentViewController = vcStores
This will just replace the window's contents with the contents in the view vcStores as defined in Interface Builder.
This video also helped, albeit for iOS and Swift 3. I was trying to create a new NSWindow at first because I'm so used to doing desktop development in Java Swing or C# Windows Forms. But I love how easy it is to just switch out the window contents like this.

How to deal with lag spike from gamecenter

My game uses Game Center for scoreboards. At the start of the app I ask Game Center to login.
The problem is that after a variable amount of time Game Center pops up with its "welcome back...." thing and this creates a HUGE latency spike in my app that is already low on resources.
I have played with the completion handlers trying to find out some sort of callback when the banner is done... but nothing!
How am I supposed to handle this? I have a loading screen, could I perhaps not finish loading until the thing has popped up?
Here is my code I use right now
func authenticateLocalPlayer()
{
Holder.loggedIn = false
print("Start Authenticate")
let localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler = {(viewController, error) -> Void in
if (viewController != nil)
{
self.presentViewController(viewController!, animated: false, completion: {self.authenticated()})
}
}
}
I've seen the same thing, and even seen differences in login times on different devices sitting next to each other on the same network. My strategy, which I believe originally came from Apple's docs or WWDC videos but I don't have a reference handy, is to initiate authentication immediately on startup, like so at in my first view controller's viewDidAppear:
Disable the start game button so users can't attempt a session when they can't even log in.
Start authentication
Do all other initiation stuff, animations, etc.
In the authentication handler, if successful, enable the start game button
This way, I move the lag pain to the app startup, which can be somewhat masked by the normal app launch delays, startup animations and what not. When I actually launch a gaming session, I've ensured that login overhead (or failure) is out of the way.
Btw, not directly related to your question, but I noticed that in your authentication handler, you present the view controller if it's present, and assume the user is authenticated if the view controller is nil. Note that when an error occurs, the error value is set but the view controller is nil. So, a nil VC can mean the user is authenticated, but it can also mean that the authentication failed. Always check the value of error first.
localPlayer.authenticateHandler = {(viewController, error) -> Void in
if (error)
{
//check and process the error.
//bail out because we are NOT authenticated
return;
}
if (viewController != nil)
{
self.presentViewController(viewController!, animated: false, completion: {self.authenticated()})
}
}

Bizarre GameKit behavior. Anyone else seeing this?

I am running Xcode 4.3.3 and am targeting iOS 5.1. I am attempting to include Game Center functionality in a game.
When authenticating the GKLocalPlayer the user is presented with the Sign in to Game Center alert view or shown to be logged in. So far this is all fine, but if the user presses the Create New Account button then any open modal views are moved behind the root view controller and the following error is spit from the console:
Unbalanced calls to begin/end appearance transitions for
<GKModalRootViewController: memory address>.
I have tried moving the GKLocalPlayer authentication code between the app delegate and the root view controller. I have also tried implementing the authentication in a new, blank project. I have tried it with and without Storyboards and ARC. In all of these cases the results were the same: modals hidden behind the root view controller and error given.
Here is the GKLocalPlayer authentication method I am calling from my app delegate’s application:didFinishLaunchingWithOptions: method:
- (void)authenticateLocalPlayer
{
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
[localPlayer authenticateWithCompletionHandler:^(NSError *error) {
if (localPlayer.isAuthenticated) {
// Perform additional tasks for the authenticated player.
} else {
// Disable Game Center features.
}
if (error) {
// Handle error.
}
}];
}
Here is a screenshot of it. In this picture the root view controller has a background with a 50% alpha value. The modal has been pushed behind the root view controller by this bug.
This stackoverflow question contains the only reference to this error (regarding GKModalRootViewController) I can find, and it doesn't fit since (a.) I’m not using cocos2d. (b.) It happens whether or not I perform a segue, and I am not touching viewWillAppear: or viewDidAppear:. (c.) No acceptable answer was given.
This question and this one seem to involve the same issue (with the view hierarchy being destroyed) but are unanswered and don’t mention the console error message.
Does this happen for anyone else? Any ideas on what could be causing this?
UPDATE 1: I went so far as to put the authentication code into an IBAction connected to a button in the modal view so as to avoid any initialization conflicts. It didn't help.
UPDATE 2: I tried moving the authentication code into a GCD background queue. he results were the same.
Here is the test project (which is GameCenter ready with my app's Bundle ID already entered).
To test:
Log out of Game Center on the test device/simulator (if you are
logged in).
Build and run the app.
Press the info button.
Press Authenticate.
When the Sign in to Game Center alert appears press Create New
Account.
Press Cancel.
Did the “Unbalanced calls...” message appear in the console? Did the
modal view (with the Authenticate button) disappear?
Press the info button.
Did the modal display again?
This bug appears on the list of "bugs Apple fixed in 6.0". I understand you would love a workaround, but when its the APIs you depend on that are buggy, chances of that are slim.
You can however rejoice that 5.x users are slowly dying out.
Best of luck with your app.