Swift FacebookSDK login bug - swift

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")
}

Related

blank collection view in user login for the first time

Good day everyone,
I have root view controller set up as my HomeViewController, in this project i am using token based authentication ( which i am storing in user defaults ) and i am using token for all my API calls.
I have a check in my viewWillAppear method to check if there is access token present and then i make the api call in viewDidAppear to populate the collection view, and this works perfectly fine at all times except the first time.
If I log in for the first time it hits the viewWillAppear, viewDidAppear and then login screen pops up and once i authenticate the user and save it in the UserDefaults, dismiss the login screen and in the HomeViewController all i get is a spinner ( which means that viewDidAppear is also been called ) but if i close the app and open it again it all works fine.
What can i change in my code to make it work in the first time please and thank you!!
class HomeViewController: UIViewController {
// MARK: - Properties
let refreshControl = UIRefreshControl()
var publishedReportList: [ReportListDetail] = []
private let reportsCollectionView: UICollectionView = {
let viewLayout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: viewLayout)
collectionView.register(ReportsCollectionViewCell.self, forCellWithReuseIdentifier: ReportsCollectionViewCell.identifier)
collectionView.backgroundColor = .systemBackground
return collectionView
}()
// MARK: - Initialisation
override func viewDidLoad() {
super.viewDidLoad()
reportsCollectionView.delegate = self
reportsCollectionView.dataSource = self
print(publishedReportList.count)
refreshControl.tintColor = .blue
refreshControl.addTarget(self, action: #selector(pullToRefresh), for: .valueChanged)
reportsCollectionView.addSubview(refreshControl)
reportsCollectionView.alwaysBounceVertical = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// check auth status
handleNotAuthenticated()
}
override func viewDidAppear(_ animated: Bool) {
// call for reports
getReportUserLayout()
}
// MARK: - Handlers
#objc func pullToRefresh() {
// Code to refresh table view
getReportUserLayout()
}
fileprivate func getReportUserLayout() {
// publishedReportList.removeAll()
whySuchEmptyLabel.isHidden = true
spinner.show(in: view)
spinner.textLabel.text = "Loading Reports.."
DispatchQueue.main.async {
ReportsManager.shared.getReportData { [weak self] (listOfReports) in
guard let strongSelf = self else { return }
strongSelf.publishedReportList = listOfReports
if listOfReports.count == 0 {
strongSelf.whySuchEmptyLabel.isHidden = false
}
strongSelf.reportsCollectionView.reloadData()
strongSelf.spinner.dismiss()
strongSelf.refreshControl.endRefreshing()
}
}
}
private func handleNotAuthenticated() {
if UserDefaults.standard.string(forKey: "accessToken") == nil {
// show login view controller
let loginVC = LoginViewController()
loginVC.modalPresentationStyle = .overCurrentContext
present(loginVC, animated: false)
}
}
}
You are in the HomeViewController and presenting loginVC, so it will not trigger viewDidAppear or viewWillAppear because it is not disappeared from the app. You have to use closure or delegate or notification to communicate back to the HomeViewController. You can also use the Combine framework and save the state. Here is an example of using delegate.
// Add protocol
protocol ViewControllerDelegate {
func loggedIn()
}
class HomeViewController: UIViewController, ViewControllerDelegate {
private func handleNotAuthenticated() {
if UserDefaults.standard.string(forKey: "accessToken") == nil {
let loginVC = LoginViewController()
loginVC.modalPresentationStyle = .overCurrentContext
loginVC.viewDelegate = self
present(loginVC, animated: false)
}
}
}
class LoginViewController: UIViewController {
var viewDelegate: ViewControllerDelegate? = nil
func userLoggedIn() {
self.viewDelegate?.loggedIn()
}
}

Sign In with Google Authentication

I've implemented Google Authentication via Firebase in my App. Everything works smoothly except for one small problem that I can't seem to find. Whenever the user opens the page that prompts them to "Sign in with Google" (ie. login page or sign up page), the banner appears momentarily before disappearing. I do not want it to appear at all, unless the user clicks the "Sign in with Google" button. How can I get rid of this?
WelcomeViewController (the view controller with the google login)
import UIKit
import FirebaseAuth
import Firebase
import FBSDKLoginKit
import GoogleSignIn
class WelcomeViewController: UIViewController, GIDSignInDelegate {
#IBOutlet weak var stackView: UIStackView!
#IBOutlet weak var signInFacebookButton: UIButton!
#IBOutlet weak var signInGoogleButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
setUpGoogleButton()
GIDSignIn.sharedInstance()?.presentingViewController = self
GIDSignIn.sharedInstance().signIn()
}
// SIGN IN WITH GOOGLE
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
if let err = error {
print("Failed to log into Google: ", err)
return
}
print("Successfully logged into Google")
guard let authentication = user.authentication else { return }
let credential = GoogleAuthProvider.credential(withIDToken: authentication.idToken, accessToken: authentication.accessToken)
Auth.auth().signIn(with: credential, completion: { (user, error) in
if let err = error {
print("Failed to create a Firebase User with Google account: ", err)
return
}
// Successfully logged in
guard let uid = user?.user.uid else { return }
print("Successfully logged into Firebase with Google", uid)
// switch to tab bar controller
let tabBarC = self.storyboard?.instantiateViewController(withIdentifier: "mainTabBarController") as! TabBarController
tabBarC.modalPresentationStyle = .fullScreen
self.present(tabBarC, animated: true, completion: nil)
print("Switched to TabBarController")
})
}
fileprivate func setUpGoogleButton() {
let button = signInGoogleButton
button?.layer.borderWidth = 0
button?.backgroundColor = UIColor.init(red: 130/255, green: 178/255, blue: 189/255, alpha: 1)
button?.layer.cornerRadius = 20.0
button?.tintColor = UIColor.white
button!.addTarget(self, action:
#selector(handleCustomGoogleSignIn), for: .touchUpInside)
GIDSignIn.sharedInstance()?.delegate = self
}
#objc func handleCustomGoogleSignIn() {
GIDSignIn.sharedInstance().signIn()
}
I've attached a link to a screen recording of what happens. The second page shown in the screen recording is identical to the code below, so it has the same problem. Any help is appreciated, thank you!
https://drive.google.com/file/d/1t4KV0Z6qwfCK56Gf2314wXWhAeQR0wUs/view?usp=sharing
That's because of your code inside viewDidLoad(). You are implementing this method:
GIDSignIn.sharedInstance().signIn()
This triggers the sign in method as soon as the view loads (as you are implementing it inside viewDidLoad()), and that causes that momentary sign in pop up that disappears.
Instead of implementing that method there, you should only implement it inside your handleCustomGoogleSignIn().
Conclusion, your viewDidLoad() should look like this:
override func viewDidLoad() {
super.viewDidLoad()
setUpGoogleButton()
GIDSignIn.sharedInstance()?.presentingViewController = self
}

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)

Swift project not segue-ing properly after Facebook login

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
}