I am currently making a small login app using Firebase. I am currently having problems with my login page.
When my users already are logged in, when they open the app, I want to change the initial view controller, so that the user can go straight to the homepage.
So my question is, what line of code do I have to perform in order to do this?
override func viewDidLoad() {
super.viewDidLoad()
if FIRAuth.auth() ? .currentUser ? .uid == nil {
notLoggedIn()
}
}
func notLoggedIn() {
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let nextViewController = storyBoard.instantiateViewController(withIdentifier: "Startpage") as!ViewController
self.present(nextViewController, animated: true, completion: nil)
}
There's a couple of ways you can do this. If you really want to change the initial view controller, you would want to NOT set an initial view controller in your storyboard, then in your app delegate's application(_:didFinishLaunchingWithOptions:) implementation, you would create a new Window object and set whichever view controller on it you want to present as the rootViewController. Then, you would call makeKeyAndVisible on that window object. Note that if you do it this way, you'll have to separately handle the case when they log out if you want to display your login window again. In that case you would just do the same thing again: make a new window object with your new ViewController object as the rootViewController and present it.
Another option is to check if they are logged in in your initial view controller's viewDidLoad method and then present your login screen if they aren't. This is what I do in one of applications where the app needs some data, either by logging into an account or manually adding it, before it can do anything.
EDIT:
Here's what my viewDidLoad, etc. looks like (note that mine project is in Objective-C, so I'm just kinda guessing without actually testing it what the correct Swift syntax is. You might need to make some adjustments) You have to dispatch the present call to the main queue because in viewDidLoad you (probably) don't have everything in order yet to actually present a new view controller (I did this quite a long time ago, so I don't recall exactly why it has to be dispatched, but because of the fact that we're already in the process of presenting the current view controller, it makes sense that you wouldn't be able to present another one at the same time. Maybe someone else can weigh in on this, because I really don't remember anymore.):
override func viewDidLoad() {
super.viewDidLoad()
if (!userLoggedIn) {
showLoginScreen()
}
}
func showLoginScreen() {
let loginViewController = storyboard?.instantiateViewController(withIdentifier: "Startpage") as! ViewController
DispatchQueue.main.async {
present(loginViewController, animated: true, completion: nil)
}
}
You can use this line of codes.
Keep in mind that you should add storyboard reference with identifier
named respectively for your need - goToLogin - in my case.
Hope It'll be helpful for anyone.
override func viewDidLoad() {
super.viewDidLoad()
Auth.auth().addStateDidChangeListener { auth, user in
if let user = user {
// User is signed in.
print("user signed in")
//Add the rest of the code here because after passig the caluses
// viewdidload will call another funxtions to it can crash
} else {
// User not signed in
self.performSegue(withIdentifier: "goToLogin", sender: Any?.self)
}
}
}
Related
I have 5 viewControllers(A,B,C,D,E), all these viewControllers are programmatically connected, and i could push and pop between them successfully.
Using:
navigationController?.pushViewController(loginVc, animated: true)
_ = navigationController?.popViewController(animated: true)
NOTE: ViewController A appears first as a default initial viewController.
Now, what i want is, when the user installs the App, only for the first time the viewController A must be shown as the initial viewController, rest of the times when the App is opened, the viewController D must be the initial viewController and from there i should be able to jump between previous viewControllers. How can i implement this. Im using Xcode 8.2, Swift 3.0
Thanks in advance.
In order to do that, you simply could add a boolean to your NSUserDefaults, using the following code:
let defaults = UserDefaults.standard
if (!defaults.bool(forKey: "firstTime")) { //will be false if does not exist yet
navigationController?.pushViewController(yourDesiredVC, animated: true) //push desired vc
defaults.set(true, forKey: "firstTime") //set the key so it never executes again
} else {
navigationController?.pushViewController(yourDesiredVC, animated: true) //push the other vc
}
Your question doesn't really say very much without some code, but one suggestion (given the current quality of the question) would be to use UserDefaults. Add your current version of the app to a key called e.g. LatestVersion. Compare it at launch with the apps current version, if they don't match, show ViewControllerA, if not show ViewControllerB.
Another way is just saving launchedForFirstTime. If its not set show ViewControllerA, however the above would take in account future versions of the app where you might want to show that view as well.
You can keep a value in UserDefaults to keep track of the returning users and check if it's there:
if let returning :Bool = UserDefaults.standard.bool(forKey: "initial_controller_shown") {
//returning user flow
} else {
//new user flow
}
A common place to check for this is in the applicationDidBecomeActive or didFinishLaunchingWithOptions
when first time your app launched then use a flag and store some value in it so that next time when your app run then you can check that whether user visit the app for the first time or not .. Now after that go to appDelegate and paste the following code in DidFinishLaunchingWithOption...
if yourFlag == true
{
let mainStoryboard: UIStoryboard = UIStoryboard(name: "MainStoreyBoard", bundle: nil)
let controller = mainStoryboard.instantiateViewController(withIdentifier: "StoreyBoardIdofYourViewController") as! UINavigationController
self.window?.rootViewController = controller
}
This will launch D viewcontroller .....
You can check it in your app delegate.m file whether the app installed first time ViewController A will appear as a initial view controller else view controller D. Check it in the following function:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
//check the initial view controller here//
return true
}
Swift 3:
let launchBefore = UserDefaults.standard.bool(forKey: "launchBefore")
if launchBefore {
print("Not first launch.") } else {
print("First launch, setting UserDefault.")
UserDefaults.standard.set(true, forKey: "launchBefore") }
You are using one Navigation Controller so it will be hard to implement your behavior. It will be much easier for you to use separate View Controllers for View A and D and call them using:
present(vc, animated: true)
and dismiss calling:
dismiss(animated: true)
*Disclaimer: I've only been coding in iOS/XCode/Swift for a couple of weeks
I have Universal Links working in that clicking a link outside my app opens up the app and I catching the Url in the AppDelegate class.
So that's all good.
My question is... how do I then redirect to the correct UIViewController AND pass the controller some info from my URL? All Universal Link tutorials stop before they get to that part.
In particular I'm confused about the lifecycles of AppDelegate and how it relates to UIViewController.
My app had two UIViewController sitting under (is this right?) a UINavigationController.
What I've tried
I have tried handling the url event in AppDelegate, and setting a public property, and then in my ViewController getting access to the AppDelegate. HOWEVER, after the Universal Link is clicked, both viewDidLoad and viewWillAppear don't get called again :-/
What's the best way to redirect to a ViewController from AppDelegate? My goal is simply to load the root view controller BUT I need to pass in some data from the URL. How?
First, Read your URL.
Get your parameters from URL
Initiate your target controller
Set your parameter to that controller
Present controller on root view controller
let urlString = url.absoluteString
let queryArray = urlString.componentsSeparatedByString("/")
if queryArray[2].lowercaseString == "yourQuery" {
let queryId = Int(queryArray[3])
if self.window?.rootViewController?.presentedViewController != nil {
self.window?.rootViewController?.dismissViewControllerAnimated(false, completion: nil)
}
let queryVC = self.window?.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier(QUERY_SCENE) as? QueryViewController
queryVC?.urlQueryId = queryId!
self.window?.rootViewController?.presentViewController(queryVC!, animated: true, completion: nil)
}
Edit:
Push a controller say 'PresentedViewController' on navigation controller and if rootViewController is also navigation controller
And on back press on controller 'OnBackPressViewController' present controller 'PresentedViewController'
if self.window?.rootViewController?.presentedViewController != nil {
self.window?.rootViewController?.dismissViewControllerAnimated(false, completion: nil)
}
let navController = self.window?.rootViewController?.storyboard?.instantiateInitialViewController() as? UINavigationController
let presentedVC = self.window?.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier(PRESENTED_SCENE) as? PresentedViewController
//Pass parameters to PresentedViewController
let onBackPressViewController = self.window?.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier(ON_BACK_PRESS_SCENE) as? OnBackPressViewController
navController!.pushViewController(onBackPressViewController!, animated: true)
navController!.pushViewController(presentedVC!, animated: true)
self.window?.rootViewController?.presentViewController(navController!, animated: true, completion: nil)
You can update your code according to that. Always remember that you have to present any view controller on rootViewController.
It depends on your app architecture. You can definitely assume that the AppDelegate has the reference to the rootViewController (window?.rootViewController). You should know what's the type of that controller. Then you can access to the other and so on. This question is, anyway, too much generic in my opinion.
How do I make my app go to a view when it detects that it enters the background? I have an authentication screen and I want that the user has to authenticate again when he comes back to the app. (Something like in 1password)
I tried to do something in appDelegate, but I just got a lot of errors.
let mapViewControllerObejct = self.storyboard?.instantiateViewControllerWithIdentifier("MainVC") as? MainVC
self.navigationController?.pushViewController(mapViewControllerObejct!, animated: false)
This is what I have tried to implement in applicationWillResignActive, but I got the error that appDelegate has no member called storyboard.
Thanks for the help
You can move to rootview controller and write this code in this function:
func applicationWillEnterForeground(application: UIApplication) {
if let navigationController = window?.rootViewController as?UINavigationController {
navigationController.popToRootViewControllerAnimated(false)
}
}
I want a way for the user to have a welcome screen / tutorial on the first launch of the app. If it isn't the first launch of the app, then it open as it usually would.
I already have the welcome screen tied to a button function if the app opens normally. I'm using BWWalkThroughViewController. Here's my code for the button function:
#IBAction func showWalkThroughButtonPressed() {
// Get view controllers and build the walkthrough
let stb = UIStoryboard(name: "MainStoryboard", bundle: nil)
let walkthrough = stb.instantiateViewControllerWithIdentifier("walk0") as! BWWalkthroughViewController
let page_one = stb.instantiateViewControllerWithIdentifier("walk1") as UIViewController
let page_two = stb.instantiateViewControllerWithIdentifier("walk2") as UIViewController
let page_three = stb.instantiateViewControllerWithIdentifier("walk3") as UIViewController
let page_four = stb.instantiateViewControllerWithIdentifier("walk4") as UIViewController
let page_five = stb.instantiateViewControllerWithIdentifier("walk5") as UIViewController
// Attach the pages to the master
walkthrough.delegate = self
walkthrough.addViewController(page_one)
walkthrough.addViewController(page_two)
walkthrough.addViewController(page_three)
walkthrough.addViewController(page_four)
walkthrough.addViewController(page_five)
self.presentViewController(walkthrough, animated: true, completion: nil)
}
func walkthroughCloseButtonPressed() {
self.dismissViewControllerAnimated(true, completion: nil)
}
That code is located in the MyTableViewController.swift file.
Here's what I can't figure out:
I want the view controllers to show on first launch. Once the user finishes the tutorial, they can press the Close button and it will close. I have the code to check if it's the app's first launch. It's located in the AppDelegate.swift file. Here's that code:
// First Launch Check
let notFirstLaunch = NSUserDefaults.standardUserDefaults().boolForKey("FirstLaunch")
if notFirstLaunch {
print("First launch, setting NSUserDefault")
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "FirstLaunch")
}
else {
print("Not first launch.")
}
return true
So how do I get the welcome screen to launch on first launch? Do I have to create a function in AppDelegate to handle that, and if so what do I have to do to make the tutorial the initial view controller for just the first launch?
I believe what you need to do is already covered here: Programmatically set the initial view controller using Storyboards. If that doesn't work for you add more notes on why the implementation failed. A google search on "programatically change uiviewcontroller on launch ios" will yield other similar links.
I am trying to switch view controllers after a user successfully logs in to their account, but it is not working correctly. I cant use a segue directly because if the login button is clicked it will go to that view controller regardless if the information is correct or not. I have tried everything that I know of with no success. This is the code I am trying.
#IBAction func loginTapped(sender: AnyObject) {
let username = usernameField.text
let password = passwordField.text
if username.isEmpty || password.isEmpty {
var emptyFieldsError:UIAlertView = UIAlertView(title: "Please try again", message: "Please fill in all the fields we can get you logged in to your account.", delegate: self, cancelButtonTitle: "Try again")
emptyFieldsError.show()
}
PFUser.logInWithUsernameInBackground(username, password:password) {
(user: PFUser?, error: NSError?) -> Void in
if user != nil {
self.performSegueWithIdentifier("Klikur", sender: self)
} else {
if let errorString = error!.userInfo?["error"] as? String {
self.errorMessage = errorString
}
self.alertView("Please try again", message: "The username password combiation you have given us does not match our records, please try again.", buttonName: "Try again")
}
}
}
I have the storyboard ID set to "Test" and it is not switching view controller when the correct information is entered. Can somebody help me resolve my problem?
[Assuming that your code is not crashing, but rather just failing to segue]
At least one problem is:
self.performSegueWithIdentifier("Test", sender: self)
should be:
dispatch_async(dispatch_get_main_queue()) {
[unowned self] in
self.performSegueWithIdentifier("Test", sender: self)
}
Remember that all UI operations must be performed on the main thread's queue. You can prove to yourself you're on the wrong thread by checking:
NSThread.isMainThread() // is going to be false in the PF completion handler
ADDENDUM
If there's any chance self might become nil, such as getting dismissed or otherwise deallocated because it's not needed, you should capture self weakly as [weak self] not unowned, and use safe unwrapping: if let s = self { s.doStuff() } or optional chaining: self?.doStuff(...)
ADDENDUM 2
This seems to be a popular answer so it's important to mention this newer alternative here:
NSOperationQueue.mainQueue().addOperationWithBlock {
[weak self] in
self?.performSegueWithIdentifier("Test", sender: self)
}
Note, from https://www.raywenderlich.com/76341/use-nsoperation-nsoperationqueue-swift:
NSOperation vs. Grand Central Dispatch (GCD)
GCD [dispatch_* calls] is a lightweight way to represent units of work that are going to be executed concurrently.
NSOperation adds a little extra overhead compared to GCD, but you can add dependency among various operations and re-use, cancel or suspend them.
ADDENDUM 3
Apple hides the single-threaded rule here:
NOTE
For the most part, use UIKit classes only from your app’s main thread.
This is particularly true for classes derived from UIResponder or that
involve manipulating your app’s user interface in any way.
SWIFT 4
DispatchQueue.main.async(){
self.performSegue(withIdentifier: "Test", sender: self)
}
Reference:
https://developer.apple.com/documentation/uikit
Make sure you're putting your:
self.performSegue(withIdentifier: ..., ...)
in viewDidAppear or later. It won't work in viewWillAppear or viewDidLoad.
I've got the same problem with login issue. probably we do the same tutorial. After naming your segue identifier you need to replace:
performSegueWithIdentifier("Klikur", sender: self)
with:
dispatch_async(dispatch_get_main_queue()){
self.performSegueWithIdentifier("Klikur", sender: self)
}
type of seque needs to be set as "show (e.g. Push)" in the storyboard segue.
Hope it will work.
The segue identifier that you pass to performSegueWithIdentifier(_:sender:) must exactly match the ID you've given the segue in the storyboard. I assume that you have a segue between the login view controller and the success view controller, which is as it should be; if not, ctrl+drag from the first to the second view controller, then select the segue's icon in the storyboard and set its ID to Klikur. Don't perform the navigation on the button click, as one commenter said, because that defeats the main purpose of having segues, which is to give a visual indication of the application flow in the storyboard.
EDIT: Here's the code for a login view controller:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var usernameField: UITextField!
#IBOutlet weak var passwordField: UITextField!
#IBAction func attemptLogin(sender: AnyObject) {
if !usernameField!.text!.isEmpty && !passwordField!.text!.isEmpty {
performSegueWithIdentifier("Klikur", sender: self)
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if "Klikur" == segue.identifier {
// Nothing really to do here, since it won't be fired unless
// shouldPerformSegueWithIdentifier() says it's ok. In a real app,
// this is where you'd pass data to the success view controller.
}
}
}
And a screenshot of the segue properties that I'm talking about:
swift 3.x
DispatchQueue.main.async(){
self.performSegue(withIdentifier: "Klikur", sender: self)
}
DispatchQueue.main.async() {
self.performSegue(withIdentifier: "GoToHomeFromSplash", sender: self)`
}
Check to make sure you are running the perform segue on a visible view controller.
This is an edge case, but my perform segue failed when I attempted to run it on the view controller belonging to my UIPageViewController that was not currently visible. It also failed if I attempted to do the segue on all view controllers belonging to my UIPageViewController, including the view controller currently visible. The fix was to track which view controller was currently visible in my UIPageViewController, and only perform the segue on that view controller.
An example in a login. When you have success in your login after clicking a button (Action) you can use:
self.performSegue(withIdentifier: "loginSucess", sender: nil)
But if you are launching the app and you got the credentials from your keychain you need to use this as a part of the theard:
DispatchQueue.main.async(){
self.performSegue(withIdentifier: "sessionSuccess", sender: nil)
}