Swift Firebase check authentication more smoothly - ios

I'm setting my window's root view controller as the login controller right off the bat, and the login controller checks user authentication from Firebase. If the user is logged in, the controller changes the root view controller to be the feed controller. Otherwise, the login controller proceeds as itself.
class LoginController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let uid = Auth.auth().currentUser?.uid {
Database.database().reference().child("users").observeSingleEvent(of: .value, with: { snapshot in
if snapshot.hasChild(uid) {
AppDelegate.launchApplication()
}
else {
self.setUpUI()
}
})
}
else {
self.setUpUI()
}
}
...
}
where launchApplication is
class func launchApplication() {
guard let window = UIApplication.shared.keyWindow else { return }
window.rootViewController = UINavigationController(rootViewController: FeedController())
}
In addition to if let uid = Auth.auth().currentUser?.uid, I'm checking whether the uid (if it isn't nil) exists in my database, because I have had the situation where a deleted user still wasn't nil.
The problem is that after the launch screen finishes, there is a moment when the login controller, though blank, is visible. Sometimes this moment lasts a few seconds. How can I check authentication such that the login controller isn't visible at all—so that the app decides how to proceed immediately after the launch screen disappears? Thanks.

1) use this following code in case if you want to set a rootController from app delegate itself . use a check if your currentUser.uid is not nil and matched with values in database then perform following code in DidFinishLaunchingWithOptions of Appdelegate . Used by me
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseApp.configure()
GIDSignIn.sharedInstance().clientID = FirebaseApp.app()?.options.clientID
if let uid = Auth.auth().currentUser?.uid {
Database.database().reference().child("users").child(uid).observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exist() {
let storyBoard = UIStoryboard.init(name: "Main", bundle: nil)
let vc = storyBoard.instantiateViewController(withIdentifier: "feedController") as! FeedController
self.window?.rootViewController=vc
self.window?.makeKeyAndVisible()
}
else {
//Present your loginController here
}
})
}
return true
}
2) Method: when you had initialised your logincontroller instead of calling a function from Appdelegate or code in LaunchApplication. make a function in login class and write the following code when required parameters are matched
var transition: UIViewAnimationOptions = .transitionFlipFromLeft
let rootviewcontroller: UIWindow = ((UIApplication.shared.delegate?.window)!)!
rootviewcontroller.rootViewController = self.storyboard?.instantiateViewController(withIdentifier: "rootnav")//rootnav is Storyboard id of my naviagtionController attached to the DestinationController [FeedController]
let mainwindow = (UIApplication.shared.delegate?.window!)!
mainwindow.backgroundColor = UIColor(hue: 0.6477, saturation: 0.6314, brightness: 0.6077, alpha: 0.8)
UIView.transition(with: mainwindow, duration: 0.55001, options: transition, animations: { () -> Void in
}) { (finished) -> Void in
}

As a quick fix, you could try checking just the desired "users" child node (and not the complete branch). For instance:
Database.database().reference().child("users").child(uid).observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exists() { ...
This could reduce your delay considerably if your database contains many users.
If that isn't enough, you might consider moving this logic to you AppDelegate class as well and show your LoginController from there (and maybe holding off your launch screen a little longer until you find out if an user is available).

Instead of changing any code, I simply set the background of the login controller to match the launch screen:
class LoginController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// here
let background_image_view = UIImageView(image: #imageLiteral(resourceName: "launch_screen_background"))
background_image_view.frame = self.view.frame
self.view.addSubview(background_image_view)
if let uid = Auth.auth().currentUser?.uid {
Database.database().reference().child("users").observeSingleEvent(of: .value, with: { snapshot in
if snapshot.hasChild(uid) {
AppDelegate.launchApplication()
}
else {
self.setUpUI()
}
})
}
else {
self.setUpUI()
}
}
...
}
There is no noticeable transition from the launch screen to the login controller.

Related

How can I switch the Bool animated in Swift (in the override ViewWillAppear methode)

I want to present one Storyboard after loading the App if the User is logged in and an other if the user have to log In.
I did it by overwriting the ViewWillAppear method, which now contains the code that is routed to the other storyboard when the user is logged in.
override func viewWillAppear(_ animated: Bool) {
let userdefaults = UserDefaults.standard
if userdefaults.string(forKey: "autoemail") != nil {
let email = userdefaults.string(forKey: "autoemail")
let password = userdefaults.string(forKey: "autopass")
Auth.auth().signIn(withEmail: email!, password: password!) { (user, error) in
self.performSegue(withIdentifier: "SignInSeguettt", sender: nil)
}
}
}override func viewWillAppear(_ animated: Bool) {
let userdefaults = UserDefaults.standard
if userdefaults.string(forKey: "autoemail") != nil {
let email = userdefaults.string(forKey: "autoemail")
let password = userdefaults.string(forKey: "autopass")
Auth.auth().signIn(withEmail: email!, password: password!) { (user, error) in
self.performSegue(withIdentifier: "SignInSeguettt", sender: nil)
}
}
}
But now the screen that would otherwise appear for a second still comes. How can I avoid that? I thought maybe by setting animated to false. I don't know how I can do this, because if I do it like that i get this Error:
Method does not override any method from its superclass
Are there other ways to get around this brief appearance of the wrong controller?
I don't think loading a view controller then making a decision whether to display it or load another view controller is the best approach to what you are trying to do.
The way I determine whether to allow the user to proceed into the app, or if they are not logged in display the login screen is done in the SceneDelegate.swift class (or could be done in the AppDelegate class if your app doesnt have a SceneDelegate)
It's done like this:
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
override init() {
FirebaseApp.configure() // Configure Firebase
}
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
var vc: UIViewController // Create a viewcontroller ready to return as the viewcontroller we are going to show to the user
if (Auth.auth().currentUser) != nil { // The users' auth token is still good, they are logged in
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil) // get a storyboard reference
// Make our holding view controller an instance of the Main storyboard's initial view controller
vc = mainStoryboard.instantiateInitialViewController()!
} else { // the user is not logged in
// Has the user seen the onboarding screen before?
if Session.HasShownOnboarding { // Yes has seen the onboardinng screens
let mainStoryboard = UIStoryboard(name: "Account", bundle: nil) // get reference to the Account storyboard
// Make our holding view controller an instance of the Account storyboard's Log In view controller
vc = mainStoryboard.instantiateViewController(identifier: "LoginViewController")
} else { // No the user has not been shown the onboarding screens yet
let launchStoryboard = UIStoryboard(name: "Onboarding", bundle: nil) // get reference to the Onboarding storyboard
// Make our holding view controller an instance of the Onboading storyboard's Onboarding view controller
vc = launchStoryboard.instantiateViewController(identifier: "OnboardingViewController")
Session.HasShownOnboarding = true
}
}
// Make the app's default view controller to display, whatever we set from the logic above, either Main, or Login, or Onboarding
self.window?.rootViewController = vc
}
// ...
}

How to dismiss current view controller from app delegate in swift?

I want to pop current view controller on some condition from appDelegate but I don't know how to do so, if any idea please help me out...................................................................................
import UIKit
import IQKeyboardManagerSwift
let kSharedAppDelegate = UIApplication.shared.delegate as? AppDelegate
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
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)
IQKeyboardManager.shared.enable = true
IQKeyboardManager.shared.shouldResignOnTouchOutside = true
IQKeyboardManager.shared.enableAutoToolbar = false
//IQKeyboardManager.shared.toolbarTintColor = .white
//IQKeyboardManager.shared.toolbarBarTintColor = ColorSet.appTheamColor
kSharedAppDelegate?.moveToSplashVC()
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Check we can access the application window
guard let window = UIApplication.shared.windows.first else {
return
}
// Check we can access the root viewController
guard let vc = window.rootViewController else {
return
}
// Check the root vc is the type that we want to dismiss
if vc is NoInternetPopUpViewController {
vc.dismiss(animated: true, completion: nil)
}
}
//MARK:- Show No Internet connection VC
func showNoInterNetVC() {
guard let controller = UIStoryboard(name: Storyboards.kNoInternet, bundle: nil).instantiateViewController(withIdentifier: Identifiers.kNoInternetPopUpViewController) as? NoInternetPopUpViewController else {return}
controller.modalPresentationStyle = .overFullScreen
controller.modalTransitionStyle = .crossDissolve
kSharedAppDelegate?.window?.rootViewController?.present(controller, animated: true, completion: nil)
//window.present(controller , animated: true)
}
}
I think pop is the wrong terminology here unless you are using a navigation controller.
If you want to dismiss the currently presented viewController you could check the rootViewController of the applications Window like this.
// Check we can access the application window
guard let window = UIApplication.shared.windows.first else {
return
}
// Check we can access the root viewController
guard let vc = window.rootViewController else {
return
}
// Check the root vc is the type that we want to dismiss
if vc is NoInternetPopUpViewController {
vc.dismiss(animated: true, completion: nil)
}
I also just noticed that you may not need to access the application singleton via the shared property, as applicationDidBecomeActive(_ application: UIApplication) is passing you the Application already - that line would become:
guard let window = application.windows.first else {
There is another way to do this, in newer iOS versions you directly have access to topViewController() so you access it like UIApplication.topViewController() the only downside of it is that you need yo wrap it into an if let statement to check if it is not null. FYI, It won’t be null in most cases if you have let your didFinishLaunching() method run at least once in your app delegate. So that it can stack a view controller to be a top view controller. This won’t be a problem for you since all of the other methods will fail as well if this is the case.
Todo a pop view controller now all you need to do it use top view controller and perform pop on its navigation view controller, or you can dismiss it in case there is no navigation view controller.

how to check is user is log in in different view controllers in firebase

I am making an app where user can upload files on cloud and retrieve later, but I am stuck.
My question is how to check if user is logged in or not,
if login page should be my view controller and every time a user opens the app they have to login or is there some way we can skip this procedure?
I tried making home page an initial view controller and checking in view didload if there is any user or not using auth.auth().currentuser.uid but I don't feel good about using it please any help would be appreciated.
I am new to firebase
if Auth.auth().currentuser.uid != nil {
//success code
}
in AppDelegate in didFinishLaunchingWithOptions
if Auth.auth().currentUser?.uid != nil {
self.window?.rootViewController = UINavigationController.init(rootViewController: viewController1())
} else {
self.window?.rootViewController = UINavigationController.init(rootViewController: viewController2())
}
to check if user is logged in with firebase, you need to implement this in the didFinishLaunchingWithOptions in the appDelegate
if Auth.auth().currentUser != nil {
// the user is logged in, you can now redirect to another view controller
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "YourStoryboardVCName") as! YourViewController
self.window?.rootViewController = vc
}
I think the below code might help you.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
configureFirebase()
configureApplicationScreen()
return true
}
// To determine which screen will be displayed when application is launched
func configureApplicationScreen() {
guard let rootNav = window?.rootViewController as? UINavigationController else {
return
}
// Might need to change the code inside if let depending your requirement
if let _ = Auth.auth().currentUser, UserDefaults.isUserLoggedIn {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let sideMenuVC = storyboard.instantiateViewController(withIdentifier: "SideMenuController")
rootNav.navigationBar.isHidden = true
rootNav.addChild(sideMenuVC)
}
}
extension UserDefaults {
static var isUserLoggedIn: Bool {
get {
return UserDefaults.standard.bool(forKey: "IsUserLoggedIn")
}
set {
UserDefaults.standard.set(newValue, forKey: "IsUserLoggedIn")
UserDefaults.standard.synchronize()
}
}
}
Once user is logged in you need to save the status in your UserDefault like
"UserDefaults.isUserLoggedIn = true" and you need to check it when the application is launched in the AppDelegate class as stated above.

Does anyone have good idea to handle the deeplink with the id which is supposed to show the single page?

Does anyone have good idea to handle the deeplink?
I want to push a single page view which needs id from the HomeViewcontroller(or anything is ok) to the single page with the id that I get from the deeplink.
My current situation is that I could get the deeplink and the id inside of that in AppDelegate file by the way like below.
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool {
if let incomingURL = userActivity.webpageURL {
let linkHandled = DynamicLinks.dynamicLinks()!.handleUniversalLink(incomingURL) { [weak self](dynamiclink, error) in
guard let strongSelf = self else { return }
if let dynamiclink = dynamiclink, let _ = dynamiclink.url {
strongSelf.handleIncomingDynamicLink(dynamicLink: dynamiclink)
}
}
return linkHandled
}
return false
}
func handleIncomingDynamicLink(dynamicLink: DynamicLink) {
guard let pathComponents = dynamicLink.url?.pathComponents else {
return
}
if pathComponents.count > 1 {
for (i, value) in pathComponents.enumerated() {
if i == 1 {
// define whether the deeplink is for topic or post
UserDefaults.standard.set(value, forKey: "deepLinkType")
print(value)
} else if i == 2 {
UserDefaults.standard.set(value, forKey: "deepLinkId")
print(value)
}
}
}
}
And then viewDidAppear in the HomeViewController
if (self.isViewLoaded && (self.view.window != nil)) {
let us = UserDefaults.standard
if let deepLinkType = us.string(forKey: "deepLinkType"), let deepLinkId = us.string(forKey: "deepLinkId"){
us.removeObject(forKey: "deepLinkType")
us.removeObject(forKey: "deepLinkId")
if deepLinkType == "topic" {
let storyboard = UIStoryboard(name: "Topic", bundle: nil)
let nextVC = storyboard.instantiateViewController(withIdentifier: "SingleKukaiVC") as! TopicViewController
nextVC.topicKey = deepLinkId
self.navigationController?.pushViewController(nextVC, animated: true)
} else if deepLinkType == "post" {
}
}
}
this works fine when the app is neither in foreground nor background I mean if it's not instanced. However, while the app is instanced, this doesn't work because viewDidAppear is not going to be read. Or even the HomeViewController itself is not might be called if user had opened another view.
So my question is that what is the best way to handle the deeplink which has id for the single page? I appreciate some examples.
Thanks in advance.
Don't write your code to push Topic view controller in the HomeViewController.
Inside the App Delegate's handleIncomingDynamicLink method, get the top most view controller, then create the view controller (as in your code) and then push it from the top most view controller.
Your code to create Topic View Controller:
let storyboard = UIStoryboard(name: "Topic", bundle: nil)
let nextVC = storyboard.instantiateViewController(withIdentifier: "SingleKukaiVC") as! TopicViewController
nextVC.topicKey = deepLinkId
Check URL to see how to fetch top most view controller
Two ways you could handle it:
For the ViewController that you want presented upon handling of the deeplink set an initializer that takes info contained in the deeplink. And set whatever you need to set to handle that info(you seem to have done that). Now in your handleIncomingDynamicLink() instantiate the ViewController we mentioned and make it to be presented. How you are going to make it present itself depends on the navigation logic that you have set.
AppDelegate receives link->Handles it->Instatiates VC and presents it->Does things accordingly
In your handleIncomingDynamicLink() use NotificationCenter to post a notification containing the info. In your ViewController add an observer for that notification and define whatever you need to be done when the notification is received.
AppDelegate receives link->Handles it->Fires notification->VC listens notifications->Does things accordingly

Sygic issue in showing the map

I have a problem with Sygic maps framework, I tried to show the map using this code
in the AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
SYContext.initWithAppKey("mhives.sdk.trial", appSecret: "2nxScQJ0s/J6FsYJ67dlQ+MZLjLzOKc0s96l0t4YyLv2IH8b31b5vWgkzbfgJZ8FYEKQtxpLFGlwyfqEQ64MSQ==") { (initResult) in
if (initResult == .success) {
self.window = UIWindow(frame: UIScreen.main.bounds)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController:ViewController = storyboard.instantiateViewController(withIdentifier: "ViewController") as! ViewController
// Set that ViewController as the rootViewController
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
initialViewController.sdkDidStart()
} else {
print("KO")
}
}
return true
}
the ViewController.swift
class ViewController: UIViewController,SYMapViewDelegate,SYRoutingDelegate {
let mapView = SYMapView()
let routing = SYRouting()
override func viewDidLoad() {
super.viewDidLoad()
}
func mapView(_ mapView: SYMapView, didFinishInitialization success: Bool) {
let tiltFor2D: SYAngle = 0
mapView.tilt = tiltFor2D
self.mapView.zoom = 10
self.mapView.rotation = 180
self.mapView.geoCenter = SYGeoCoordinate(latitude: 48.147, longitude: 17.101878)!
self.mapView.frame = self.view.bounds
view.addSubview(self.mapView)
}
func computeRoute(from fromCoordinate: SYGeoCoordinate, to toCoordinate: SYGeoCoordinate) {
// Create an instance of SYRouting
// Make self a delegate for SYRouting to receive and handle SYRoutingDelegate responses
// Create SYWaypoint from coordinate. Set correct SYWaypointType for start and finish.
let startWaypoint = SYWaypoint(position: fromCoordinate, type: .start, name: nil)
let endWaypoint = SYWaypoint(position: toCoordinate, type: .end, name: nil)
// Optionally: create an instance of SYRoutingOptions for configuring computing of a route
let routingOptions = SYRoutingOptions()
routingOptions.transportMode = .pedestrian // For other options see SYTransportMode
routingOptions.routingType = .economic// For other options see SYRoutingType
// Start computing route
self.routing.computeRoute(startWaypoint, to: endWaypoint, via: nil, with: routingOptions)
}
func routing(_ routing: SYRouting, computingFailedWithError error: SYRoutingError) {
print(error.rawValue)
}
func routing(_ routing: SYRouting, didComputePrimaryRoute route: SYRoute?) {
SYNavigation.shared().start(with: route)
// You might want to put it also on the map
let mapRoute = SYMapRoute(route: route!, type: .primary)
mapView.add(mapRoute)
}
Now I try to show simply the Sygic maps but I didn't get it and I receive this message in the debugger "Sygic: W 18/08/19 17:04:51 No SSO session, unable to send http request!"
func sdkDidStart(){
mapView.delegate = self
routing.delegate = self
self.mapView.frame = self.view.bounds
self.view.addSubview(self.mapView)
let start = SYGeoCoordinate(latitude: 37.276758, longitude: 9.864160900000002)
let end = SYGeoCoordinate(latitude: 37.25408, longitude: 9.906733)
//computeRoute(from: start!, to: end!)
}
Any help please
First of all do you have valid key and secret? If not you can request one here: https://www.sygic.com/business/request-sygic-mobile-sdk-api-key?product=mobileSdk
I understand you are not using real one here in example, but at least the error seems like the one you are using isn't valid.
The other thing is, when do you initialize your SYMapView? If you load ViewController as initial view controller using storyboard for example, then it will try to load map view instance and all of its underlying components before the SDK is fully loaded. Be careful, you should only work with SDK after it is successfully initialized.
So check your API keys and try to initialise that view controller in SYContext.initWithAppKey() completion handler.

Resources