Swift project not segue-ing properly after Facebook login - swift

The initial ViewController, LoginViewController.swift:
import UIKit
class LoginViewController: UIViewController, FBSDKLoginButtonDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
if (FBSDKAccessToken.currentAccessToken() != nil)
{
// User is already logged in, do work such as go to next view controller.
performSegueWithIdentifier("loginSegue", sender: nil)
print("segued due to login")
}
else
{
let loginView : FBSDKLoginButton = FBSDKLoginButton()
self.view.addSubview(loginView)
loginView.center = self.view.center
loginView.readPermissions = ["public_profile", "user_friends"]
loginView.delegate = self
}
}
func loginButton(loginButton: FBSDKLoginButton!, didCompleteWithResult result: FBSDKLoginManagerLoginResult!, error: NSError!) {
print("User Logged In")
if ((error) != nil)
{
// Process error
}
else if result.isCancelled {
// Handle cancellations
}
else {
// If you ask for multiple permissions at once, you
// should check if specific permissions missing
performSegueWithIdentifier("loginSegue", sender: nil)
/*
if result.grantedPermissions.contains("email")
{
// Do work
}
*/
}
}
func loginButtonDidLogOut(loginButton: FBSDKLoginButton!) {
print("User Logged Out")
}
}
"segued due to login" is printed to the terminal upon starting up the app every time, so the if-statement is clearly being reached and also the performSegueWithIdentifier() line. However, the segue is not actually performed as the LoginViewController stays on the screen and the next ViewController is not displayed. I have also tried adding the line:
performSegueWithIdentifier("loginSegue", sender: nil)
in several other locations I know the program is reaching, like right after super.viewDidLoad(). So, the problem seems to be specific to the segue and the problem does not seem to be with Facebook's login.
I have included a screenshot of the storyboard with the segue's attributes:
I can include any other files if needed. I also suspect it could be a similar type bug as this stackoverflow problem. I have tried deleting the placeholders in my UITextViews in all of my ViewControllers, but this did not solve the problem.

Ok so here's how your application:didFinishLaunching:withOptions should look like.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
let mainStoryboard = UIStoryboard.init(name: "Main", bundle: nil)
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
var initialViewController: UIViewController
if(FBSDKAccessToken.currentAccessToken() != nil){
let vc = mainStoryboard.instantiateViewControllerWithIdentifier("someOtherViewController") as! SomeOtherViewController
initialViewController = vc
}else{
initialViewController = mainStoryboard.instantiateViewControllerWithIdentifier("loginViewController")
}
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
return true
}

Related

UserDefaults not working on didload in my project

I have a login screen LoginViewController and homescreen HomeScreenController which is pushed when the user is successfully logged in).
I have declared a variable for UserDefaults as below in a seperate class:
class User {
static var isLoggedIn = UserDefaults.standard.bool(forKey:"islogged")
}
When the user clicks login Button, the code is as below
ButtonAction:
{
User.isLoggedin = true
//Performsegue........to homescreen
}
ViewDidload {
if user.isloggedin == true {
//Performsegue......homescreen
}
else {
Show some alerts
}
}
Here in my case...nothing happens...sorry for the rough code since I am using my smartphone to ask this question.
Am I missing something in appdelegate or somewhere else.
I just want to use the variables as it is because I don't want to change the entire code in my project.
The project is actually complicated and I just made an example to make you guys understand my situation.
This is not the exact code.
Using a class with a static variable for a UserDefaults value is bad practice. Apart from that setting the static variable doesn't update the value in UserDefaults.
In the button action set the value in UserDefaults and perform the segue
#IBAction func login(_ sender : Any) {
UserDefaults.standard.set(true, forKey:"islogged")
performSegue(...
}
In viewDidLoad get the value directly from UserDefaults
func viewDidLoad() {
super.viewDidLoad()
if UserDefaults.standard.bool(forKey:"islogged") {
performSegue(...
} else {
// present error
}
}
I think this is happening coz you are calling the UserDefaults within the User class. This does not get executed until the class is initialized.
Try to do this directly in viewDidLoad(), it should work.
if UserDefaults.standard.bool(forKey:"islogged") {
Performsegue......homescreen
}else{
Show some alerts
}
Try this instead of your isLoggedIn line of code
static var isLoggedIn: Bool {
get {
return UserDefaults.standard.bool(forKey: "isLoggedIn")
}
set {
UserDefaults.standard.setValue(newValue, forKey: "isLoggedIn")
UserDefaults.standard.synchronize()
}
}
I believe you have a problem with the call to performSegue:. You cannot call this method on viewDidLoad because the viewcontroller is not yet fully setup. Try moving your viewDidLoad to viewDidAppear
Move viewDidLoad code to viewDidAppear
override func viewDidAppear(animated: Bool){
super.viewDidAppear(animated)
if User.isLoggedIn{
performSegue("someIdentifier")
}else{
//show some alerts.
}
}
Also, use a computed var for your isLoggedIn.
taken from Marco:
static var isLoggedIn: Bool {
get {
return UserDefaults.standard.bool(forKey: "islogged")
}
set(aIsLoggedIn) {
UserDefaults.standard.set(aIsLoggedIn, forKey: "islogged")
}
}
Perform Segue on ViewDidLoad
I think you're probably trying to achieve this:
class User {
static var isLoggedIn: Bool {
get {
return UserDefaults.standard.bool(forKey: "islogged")
}
set(aIsLoggedIn) {
UserDefaults.standard.set(aIsLoggedIn, forKey: "islogged")
}
}
}
The problem with your code is that you don't save the state of isLoggedIn, so at every startup the value is always false
In your code
class User {
static var isLoggedIn = UserDefaults.standard.bool(forKey:"islogged")
}
The 'isLoggedIn' is just a value type. It will not change by UserDefaults.standard's some value's changing. Because your declare is just set a default value to that.if you want to have a variable to imply the global unique semantic, you must insure that modify it in the same address, and get it from the same address too.
I think you need to hide login vc if user was logined, right?
Do it in HomeVC
#IBAction func login(_ sender : Any) {
if login == true{
UserDefaults.standart.set("true", forKey:"islogged")
}
}
And paste it to AppDelegate
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
self.window = UIWindow(frame: UIScreen.main.bounds)
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
var initialViewController: UIViewController
if UserDefaults.standard.string(forKey: "islogged") == "true"//your condition if user is already logged in or not
{
// if already logged in then redirect to MainViewController
initialViewController = mainStoryboard.instantiateViewController(withIdentifier: "HomeVC") as! UITabBarController // 'MainController' is the storyboard id of HomeViewController
}
else
{
//If not logged in then show LoginViewController
initialViewController = mainStoryboard.instantiateViewController(withIdentifier: "LoginVC") as! SignInViewController // 'LoginController' is the storyboard id of LoginViewController
}
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
return true
}

Google sign out and return to login page

I want to click the Sign Out button on the ListViewController to sign out the user, and return to the SignInViewController, like below picture.
In ListViewController for the SignOut button:
#IBAction func didTapSignOut(_ sender: Any) {
//Sign Out
GIDSignIn.sharedInstance().signOut()
//Go to the `SignInViewController`
let mainStoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let desVC = mainStoryboard.instantiateViewController(withIdentifier: "SignInViewController") as! SignInViewController
self.navigationController?.pushViewController(desVC, animated: true)
}
When back to the SignInViewController, there's a back button, which is directed back to the ListViewController on it. It seems like the app still has the cache of the user's data so the user doesn't actually sign out.
But what I want is to go back to the app's initial state that the user has to sign in again.
My Storyboard:
How I move from SignInViewController to ListViewController: in AppDelegate
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!,
withError error: Error!) {
if let error = error {
print("\(error.localizedDescription)")
} else {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let tabbarVC = storyboard.instantiateViewController(withIdentifier: "TabbarIdentifier") as! UITabBarController
self.window?.rootViewController?.present(tabbarVC, animated: false, completion: nil)
}
}
Solution I've tried:
var window: UIWindow?
#IBAction func didTapSignOut(_ sender: Any) {
GIDSignIn.sharedInstance().signOut()
let desVC: UIViewController = SignInViewController()
if let window = self.window{
window.rootViewController = desVC
}
self.navigationController?.popToRootViewController(animated: true)
}
but now the view didn't change after I clicked the button.
In google login you are presenting the tabbar controller from signinViewcontroller. So just dismiss the tabbar controller when logout button is tapped
#IBAction func didTapSignOut(_ sender: Any) {
//Sign Out
GIDSignIn.sharedInstance().signOut()
//Go to the `SignInViewController`
self.tabBarController?.dismiss(animated: true, completion: nil)
}
change self.navigationController?.pushViewController to self.navigationController?.popToRootViewController (this will have a pop animation)
if you need a push animation, use self.navigationController?.setViewControllers([desVC])
UPDATED 1:
If you don't mind having no push/pop animation, you can directly change the window
let nvc = UINavigationController(rootViewController: desVC)
let window = UIApplication.shared.window
window.rootViewController = nvc
UPDATED 2:
Yea, updating via navigationController would not be proper in your hierarchy. Just change the rootViewController of the window (as stated in Updated 1)

Blank screen first time launching app xcode

I am having a little issue with logging into an app the first time with a blank screen and I am getting the warning "Attempt to present on whose view is not in the window hierarchy!" After I close and relaunch, the views appear fine. I believe it has something to do with the rootViewController but not sure... Thanks in advance for any help or direction!
App delegate
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var ref:FIRDatabaseReference?
var databaseHandle:FIRDatabaseHandle?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
window?.rootViewController = MainNavigationController()
FIRApp.configure()
ref = FIRDatabase.database().reference()
return true
}
Navigation controller as rootViewController
class MainNavigationController: UINavigationController {
var segmentedController: UISegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
let vc1 = TravelersFeedVC()
let vc2 = ProfileVC()
if isLoggedIn() {
// assume user is logged in
let homeController = HomeController()
viewControllers = [homeController]
homeController.firstViewController = vc1
homeController.secondViewController = vc2
} else {
perform(#selector(showLoginController), with: nil, afterDelay: 0.01)
}
}
fileprivate func isLoggedIn() -> Bool {
return UserDefaults.standard.isLoggedIn()
}
func showLoginController() {
let loginController = LoginController()
present(loginController, animated: true, completion: {
// perhaps do something here later
})
}
}
// log in function called
func finishLoggingIn() {
let rootViewController = UIApplication.shared.keyWindow?.rootViewController
guard let mainNavigationController = rootViewController as? MainNavigationController else { return }
let vc1 = TravelersFeedVC()
let vc2 = ProfileVC()
if isLoggedIn() {
// assume user is logged in
let homeController = HomeController()
mainNavigationController.viewControllers = [HomeController()]
homeController.firstViewController = vc1
homeController.secondViewController = vc2
} else {
perform(#selector(showLoginController), with: nil, afterDelay: 0.01)
}
UserDefaults.standard.setIsLoggedIn(value: true)
dismiss(animated: true, completion: nil)
}
Ok, so my last answer was wrong (i deleted it), the things is that in app there is a keywindow, which is your navcontroller, and you cannot present anything on this one untill it will load its subviews (even if it hasn't any), and that would happend on viewdidappear, so you should put your code from viewdidload there.

iOS firebase: FIRAuthUIDelegate.authUI not being called

I am trying to launch google login from AppDelegate.swift and then launch my app's main screen upon login success.
I am able to
show the google login button as shown above
the user is sent to google to sign in
the user is sent back to original (step 1)
After step 3. I'd like to send the user to my app's main page.
My code is below. The problem I'm having is that authUI is not being called.
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, FIRAuthUIDelegate {
var window: UIWindow?
var authUI: FIRAuthUI?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
FIRApp.configure()
authUI = FIRAuthUI.defaultAuthUI()
authUI?.delegate = self
let providers: [FIRAuthProviderUI] = [FIRGoogleAuthUI()]
authUI?.providers = providers
// show google login button
let authViewController = authUI?.authViewController()
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window?.rootViewController = authViewController
self.window?.makeKeyAndVisible()
return true
}
func application(application: UIApplication, openURL url: NSURL, options: [String: AnyObject]) -> Bool {
return GIDSignIn.sharedInstance().handleURL(url, sourceApplication: options[UIApplicationOpenURLOptionsSourceApplicationKey] as? String, annotation: options[UIApplicationOpenURLOptionsAnnotationKey])
}
func authUI(authUI: FIRAuthUI, didSignInWithUser user: FIRUser?, error: NSError?) {
// launch main view controller
}
}
EDIT: This appears to be a duplicate of another question. The other question's title is quite general and only gets to the details a few lines deep. In any case, I believe Chris's answer is more thorough than the one there. I think both the question and answers here are clearer, more pointed and more thorough so it would be a mistake to just direct people here to go there as would happen if this was marked as a duplicate.
I think your problem lies here, in the - (void)signInWithProviderUI:(id<FIRAuthProviderUI>)providerUI method.
The delegate method is called in the dismissViewControllerAnimated:completion: completion block.
[self.navigationController dismissViewControllerAnimated:YES completion:^{
[self.authUI invokeResultCallbackWithUser:user error:error];
}];
As you can see from the Apple docs, this method is expected to be called on a modally presented viewController. You are displaying it as a root view controller. Try displaying it with a modal from a UIViewController, and things should work out. To debug this try and set a breakpoint at line 193 to see that it won't get hit. I would be very surprised if this doesn't work when you display the authController modally.
To come up with a possible solution to your problem (I am assuming you want to ensure a user is signed in before using your app). The below is a simplification of what I am using in an app currently.
EDIT: Updated for the new 1.0.0 FirebaseUI syntax.
class MainTabController: UITabBarController, FIRAuthUIDelegate {
let authUI: FUIAuth? = FUIAuth.defaultAuthUI()
override func viewDidLoad() {
super.viewDidLoad()
var authProviders = [FUIFacebookAuth(), FUIGoogleAuth()]
authUI.delegate = self
authUI.providers = authProviders
//I use this method for signing out when I'm developing
//try! FIRAuth.auth()?.signOut()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !isUserSignedIn() {
showLoginView()
}
}
private func isUserSignedIn() -> Bool {
guard FIRAuth.auth()?.currentUser != nil else { return false }
return true
}
private func showLoginView() {
if let authVC = FUIAuth.defaultAuthUI()?.authViewController() {
present(authVC, animated: true, completion: nil)
}
}
func authUI(_ authUI: FUIAuth, didSignInWith user: FIRUser?, error: Error?) {
guard let user = user else {
print(error)
return
}
...
}
It must be a problem of reference.
class AppDelegate: UIResponder, UIApplicationDelegate, FIRAuthUIDelegate {
var window: UIWindow?
let authUI = FIRAuthUI.defaultAuthUI()
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
FIRApp.configure()
authUI.delegate = self
let providers: [FIRAuthProviderUI] = [FIRGoogleAuthUI()]
authUI.providers = providers
// show google login button
let authViewController = authUI.authViewController()
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window?.rootViewController = authViewController
self.window?.makeKeyAndVisible()
return true
}
}
Try this. AppDelegate will hold the reference of authUI and its delegate.

Swift FacebookSDK login bug

I face a strange issue using Facebook SDK to implement login.
I have 2 views :
- login view, with a facebook button
- welcome view, with access restricted to logged user
My code in login view :
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if ( FBSDKAccessToken.currentAccessToken() != nil){
let homePage = self.storyboard?.instantiateViewControllerWithIdentifier("SWRevealViewController") as! SWRevealViewController
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window?.rootViewController = homePage
}
}
func loginButton(loginButton: FBSDKLoginButton!, didCompleteWithResult result: FBSDKLoginManagerLoginResult!, error: NSError!) {
if error != nil {
print(error.localizedDescription)
return
}
if let _:FBSDKAccessToken = result.token {
let homePage = self.storyboard?.instantiateViewControllerWithIdentifier("SWRevealViewController") as! SWRevealViewController
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window?.rootViewController = homePage
}
}
In the welcome view I just put a WELCOME message on the console.
The bug is the following: when my user is not already logged, I see twice WELCOME messages.
That means that 2 threads go to welcome view: one with login button, and another with viewDidload().
What is the best way to implement the login to avoid this issue?
Thank you for your feedback.
EDIT
Welcome View code:
override func viewDidLoad() {
super.viewDidLoad()
if (self.revealViewController() != nil) {
self.menuButton.target = self.revealViewController()
self.menuButton.action = "revealToggle:"
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
}
print("WELCOME")
}