Navigation after google sign in | UIKit - swift

I am trying to Navigate to my main content after integration google sign in a separate Login Page. Before creating the login page, my app's entry point was the Navigation controller.. Now I needed users to login before that so I added a View Controller with google sign in button, and the sign in is working fine. this is my storyboard:
Now I want if the sign in succeed, to display the app's main content view (which is as if the entry point is the Navigation Controller from now on). I tried the following approach:
#IBAction func signIn(_ sender: Any) {
let signInConfig = GIDConfiguration.init(clientID: "xyz.apps.googleusercontent.com")
GIDSignIn.sharedInstance.signIn(with: signInConfig, presenting: self) { user, error in
guard error == nil else { return }
// If sign in succeeded, display the app's main content View.
let vc = self.storyboard?.instantiateViewController(withIdentifier: "scanReceiptsViewController") as! ViewController
self.navigationController?.pushViewController(vc, animated: true)
self.present(vc, animated: true, completion: nil)
}
}
There are 2 problems:
the new viewcontroller is presented modally, users can very easily go back to the login page and it distrubs the flow of my app
user will need to login everytime to navigate to the main content view controller, I want that if signed in the entry point becomes the main storyboard from now on. if that makes sense
Thank you and sorry I'm new into learning swift!

I would recommend that you keep your navigation controller (I will refer to it from now on as ScanRecipientsViewController) as the default app entry point, since users only need to see the login screen if they are not logged in, which is most of the time not the case. This will give us the benefit of a slightly faster and smoother performance at the app start when the user is logged in.
You could handle this case in your AppDelegate's func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool method. For example, check if the user is logged in, if not, set your LoginController as the new root controller, which will replace your ScanRecipientsViewController that was set as root by the storyboard.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if GIDSignIn.sharedInstance.currentUser == nil { // just a guess, please refer to the documentation on how to check if the user is logged in
let storyboard = UIStoryboard(name: "Login", bundle: nil)
/*
If you use the SceneDelegate in your project, you will need to retrieve the window object from it instead of from the AppDelegate.
let window = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window
*/
window?.rootViewController = storyboard.instantiateViewController(withIdentifier: "LoginController")
}
return true
}
After the user successfully logs in, you can create a new instance of your ScanRecipientsViewController and set it as the root controller back again. You can do this by e.g. creating a public method in your AppDelegate:
extension AppDelegate {
func openRecipientsController() {
let storyboard = UIStoryboard(name: "Home", bundle: nil)
/*
If you use the SceneDelegate in your project, you will need to retrieve the window object from it instead of from the AppDelegate.
let window = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window
*/
window?.rootViewController = storyboard.instantiateInitialViewController()
}
}
and in your LoginViewController:
#IBAction func signIn(_ sender: Any) {
let signInConfig = GIDConfiguration.init(clientID: "xyz.apps.googleusercontent.com")
GIDSignIn.sharedInstance.signIn(with: signInConfig, presenting: self) { user, error in
guard error == nil else { return }
// If sign in succeeded, display the app's main content View.
(UIApplication.shared.delegate as? AppDelegate)?.openRecipientsController()
}
}
As a last thing, you normally don't use both pushing and presenting at the same time, you need to choose one of the two options.
let vc = self.storyboard?.instantiateViewController(withIdentifier: "scanReceiptsViewController") as! ViewController
self.navigationController?.pushViewController(vc, animated: true)
self.present(vc, animated: true, completion: nil)
I wish you lots of fun learning iOS development with Swift!

Related

Whose view is not in the window hierarchy only in First Launch

I have an Onboarding screen that I'm showing to the new users when they first open the app.
In my appDelegate I check whether is the first launch or not.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.main.bounds)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
var initialViewController = storyboard.instantiateViewController(withIdentifier: "OnBoarding")
let userDefaults = UserDefaults.standard
if userDefaults.bool(forKey: "onBoardingComplete") {
initialViewController = storyboard.instantiateViewController(withIdentifier: "MainApp")
}
window?.rootViewController = initialViewController
window?.makeKeyAndVisible()
}
Also I have a collectionViewCell that I have some buttons and when I click them I get an Alert with informations.
Example of one button
#IBAction func guide3Btn(_ sender: Any) {
let infoVC = infoService.info(title: "Title", body: "Information")
self.window?.rootViewController?.present(infoVC, animated: true, completion: nil)
}
When the user first launches the app if he clicks the info button gets this:
Warning: Attempt to present <MyApp.InfoViewController: 0x7f91db45cfb0> on <MyApp.OnbBoardViewController: 0x7f91db506af0> whose view is not in the window hierarchy!
If the user reopens the app everything is ok. I know that when we have first launch we have onBoarding as root controller but I can't understand how to fix this.
Update
This is the infoService class. I use a new storyboard to create the alert.
class InfoService {
func info(title: String, body: String) -> InfoViewController {
let storyboard = UIStoryboard(name: "InfoStoryboard", bundle: .main)
let infoVC = storyboard.instantiateViewController(withIdentifier: "InfoVC") as! InfoViewController
infoVC.infoBody = body
infoVC.infoTitle = title
return infoVC
}
}
You can try add your storyboard instantiate code blocks to main thread using DispatchQueue.main.async like below:
I solved almost all of my whose view is not in the window hierarchy! problems.
DispatchQueue.main.async {
let infoVC = storyboard.instantiateViewController(withIdentifier: "InfoVC") as! InfoViewController
infoVC.infoBody = body
infoVC.infoTitle = title
}
return infoVC
Referenced from : https://stackoverflow.com/a/45126338/4442254

Select storyboard to launch on launchScreen completion

After completion of launchScreeen(Splash) I want to be able to select which storyboard to launch. For example launch login.storyboard if user is not logged in or launch dashboard.storyboard if user is logged in. Currently Main.storyboard is launched after launchScreen. in appdelegate I have a code for checking the login status as follows:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
var launchDashBoard = false
let isUserLoggedIn = UserDefaults.standard.object(forKey: TAG_IS_USER_LOGGEDIN) as? Bool
if isUserLoggedIn != nil {
launchDashBoard = isUserLoggedIn!
}
if launchDashBoard {
self.loadDashBoard()
}else{
self.loadIntro()
}
return true
}
func loadHome(){
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let home = storyboard.instantiateViewController(withIdentifier: "dashboard") as! Dashboard
let navigationController = UINavigationController(rootViewController: home)
self.window?.rootViewController = navigationController
}
func loadLogin(_ viewController: UIViewController?){
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let home = storyboard.instantiateViewController(withIdentifier: "signIn") as! SignInVC
home.previousViewController = viewController
let navigationController = UINavigationController(rootViewController: home)
self.window?.rootViewController = navigationController
}
If I run the app with this code it crashes with following log:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Pushing a navigation controller is not supported'
*** First throw call stack:
Is there a way to include a function that determines which storyboard to launch on launchScreen loading is finished or is there something wrong with my code?
You can create a new Navigation.storyboard. That storyboard can have an initial view controller(Call it StartUpNavigationViewController), that will be opened when Launch screen has been shown.
In that StartUpNavigationViewController, check for the logic if the user is logged in or not, based on that you can navigate to login or dashboard storyboard.

Present login view controller modally in swift tabbed application

I'm trying to present a view controller modally from my app delegate, however, I'm receiving the below error. When creating my project I chose a tabbed application. I need help resolving this error and presenting the view controller. Would also appreciate code to dismiss the view controller too
2018-10-03 20:28:57.324 App[74397:2825933] Warning: Attempt to present <App.SignUpViewController: 0x7fbd7b608c60> on <UITabBarController: 0x7fbd7b407620> whose view is not in the window hierarchy!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
showSignUpView()
return true
}
func showSignUpView() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let signUpViewController = storyboard.instantiateViewController(withIdentifier:"SignUpViewController") as! SignUpViewController
signUpViewController.modalPresentationStyle = UIModalPresentationStyle.fullScreen
signUpViewController.modalTransitionStyle = UIModalTransitionStyle.coverVertical
window?.rootViewController?.present(signUpViewController, animated: true, completion: nil)
}

How can I make a 3d Touch Quick Action open a certain view controller in my app?

I have an app that has a 3D Touch quick action that I want to have it launch a certain view controller in my app.
Could someone help inform me what the correct code would be to launch a certain view controller in my app?
In my AppDelegate.swift I have this... but it's not working.
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: #escaping (Bool) -> Void) {
if shortcutItem.type == "Matt-Held.blackshirtMarket.viewproducts" {
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
segue.destination as!
productViewController
}
The view controller I want to "launch" when the 3d quick action is pressed is called "productViewController"
Thanks in advance!
You want to instantiate your target VC from the storyboard, like this:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let targetVC = storyboard.instantiateViewController(withIdentifier :"productViewController") as! ProductViewController
Then you want to decide what to do with it. Some common options might be a) present it modally, b) push it onto an existing Nav Controller, or c) just make it the rootVC of your app's window. Here's how you would do the latter:
// option B) push onto an existing nav controller
if let navC = window?.rootViewController as! UINavigationController? {
navC.pushViewController(targetVC, animated: false)
}
// option C) just make it the root VC
window?.rootViewController = targetVC
I think the other methods are well documented elsewhere. What you want depends on the navigation structure of your app (which you didn't include in the question).
Apart from that, do take a look at the sample shortcuts application. And remember that this method, performActionForShortcut, is called when a quick action is used to activate your app from the background. So you will also need to deal with launching your app via a shortcut.

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
}