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)
}
Related
I'm having a problem where every time I enter the right credentials, it brings me to one view controller then opens up the same view controller again even though I only have the login viewer controller linked to one view controller. If I don't enter the right credentials it still brings me into the linked view controller. Here is the code.
EDIT: Using a push segue(show)
#IBAction func loginTapped(_ sender: Any) {
if let Email = userEmail.text, let Pass = userPassword.text{
Auth.auth().signIn(withEmail: Email, password: Pass, completion: { (user, error) in
if error != nil{
print("incorrect")
}
else{
if error == nil{
self.performSegue(withIdentifier: "loginPage", sender: self)
print("correct")
}
}
})
}
}
I don't know if you've fixed your problem, but check your storyboard. Sounds like you have a segue connected from the button to the next ViewController which would result in pressing the button and it'll always push that ViewController.
To do this easily just see if you have a segue connected from the button to your destination ViewController in your MainStoryboard.
what i am trying to accomplish is once the app is launched it will check for first time use. if it is the first time use it will take you to a view controller to enter credentials, else it will take you to to the main menu of the app. this is what i have so far but every time i launch it will give me a blank page with the error message of "A segue must either have a performHandler or it must override -perform.
" i have both segues linked on storyboard. can any one please steer me in the right direction.
let defaults = UserDefaults.standard
if defaults.string(forKey: "isAppAlreadyLaunchedOnce") != nil{
print("first time")
self.performSegue(withIdentifier: "toToken", sender: nil)
}else{
defaults.set(true, forKey: "isAppAlreadyLaunchedOne")
defaults.synchronize()
print("not first")
self.performSegue(withIdentifier: "toMainMenu", sender: nil)
}
If your segue type is set to be Custom in Storyboard — you have to subclass UIStoryboardSegue with your own logic in order for it to work.
class MySegue: UIStoryboardSegue {
override func perform() {
// your custom transition logic
}
}
Otherwise just use one of the existing presets from iOS SDK.
Your method is very confusing. The way I go about this problem is shown below.
override viewDidLoad() {
super.viewDidLoad
let checkForFirstTimeLaunch = Userdefaults.standard.string(forKey: "Flag")
if checkForFirstTimeLaunch == false {
print("First time launch")
//if user launches app for the first time, it will go here
} else {
//otherwise, it will go here
}
}
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)
}
}
}
I'm trying to perform a segue to a new view controller, but the segue is being called twice and the new view controller appears twice.I'm using a method that performs a GET request to an API to retrieve data.That method uses a completion handler.
func getSearchResultsForQuery(_ query: String, completionHandlerForSearchResultsForQuery: #escaping (_ success: Bool, _ error: NSError?) -> Void)
When the method completes successfully my segue is called, from within the main queue as is required.
I've set breakpoints so I could see what was going on and the execution jumps from the performSegue back up to the conditional that checks if the method was successful and then continues until the segue is called a second time. I've tried a purely programatic segue, but the result was the same.I also added a print statement, and if I comment out the segue the print statement is only called once.
I've used this same pattern a number of times before and never had a problem with it and I just can't figure out why this is happening.The only thing I'm doing different this time is using Swift 3 and using DispatchQueue.main.async instead of dispatch_async(dispatch_get_main_queue(). Here is the function which is giving me this problem:
#IBAction func search(_ sender: UIButton) {
let searchQuery = searchField.text
TIClient.sharedInstance().getSearchResultsForQuery(searchQuery!) { (success, error) in
if success {
print("Food items fetch successful")
DispatchQueue.main.async {
print("Perorming segue for food item: \(searchQuery)")
self.performSegue(withIdentifier: "showFoodItems", sender: self)
}
} else {
print("error: \(error)")
}
}
}
Edit: I never found out what the problem was, but completely deleting the story board and recreating it solved it.
I know this isn't a great way to fix this issue, Also I can't leave a comment due to low reputation but what happens if you wrap the whole if statement in DispatchQueue.main?
#IBAction func search(_ sender: UIButton) {
let searchQuery = searchField.text
TIClient.sharedInstance().getSearchResultsForQuery(searchQuery!) { (success, error) in
DispatchQueue.main.async {
if success {
print("Food items fetch successful")
self.performSegue(withIdentifier: "showFoodItems", sender: self)
} else {
print("error")
}
}
}
Would that yield a different result or still the same result? checking for Bool doesn't require too much processing power so I don't think putting it in a main queue is a bad thing but I'd do this to trouble shoot. Sorry I can't just comment on this.
Check in storyboard, maybe you set segue from your button action instead of controller.
I'm loading some XML from a webservice (car data), create some car objects and would like to display them in a TableViewController.
When the user has selected start and destination location, I'm making an async call to the webservice, show an activity indicator and as soon as the data is loaded, I go to a new view. So I have something like this:
class NewReservationViewController : UIViewController {
#IBAction func searchCarsClicked(sender: UIBarButtonItem) {
//show load cars activity indicator
loadingCarsActivityIndicator.startAnimating()
//load available cars from webservice asyncronously
DataManager.getAvailableCars([...parameter list...], carsLoadedCallback: carsLoaded)
}
func carsLoaded(loadedCars: [Car]) {
//dismiss the waiting widget
//trigger the segue and advance to the next screen
loadingCarsActivityIndicator.stopAnimating()
print("stopped cars loading activity indicator")
print("cars loaded callback called")
print("loaded \(loadedCars.count) distinct cars")
self.cars = loadedCars
performSegueWithIdentifier("showAvailableCars", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showAvailableCars" {
let carTableViewController = segue.destinationViewController as! CarTableViewController
carTableViewController.cars = self.cars
presentViewController(carTableViewController, animated: false, completion: nil)
}
}
}
class DataManager {
class func getAvailableCars([...parameter list...], carsLoadedCallback: ([Car]) -> Void){
Webservice.getAvailability([...parameter list...], completionHandler: { (data, response, error) in
//parse xml
//in the end I get an Array of Car objects
var cars: [Car] = ...
carsLoadedCallback(cars)
})}
}
}
When I populate the TableView with some DummyData I create within the CarTableViewController class, it works fine. However when I try to pass the car arrays to my TableViewController I get a EXC_BAD_ACCESS code = 2 exception. As far as I know this is some kind of Memory exception that is usually caused by a corrupt pointer. So I guess that the car array I created in my static DataManager class within the static method I called gets destroyed. However I'm not sure about that because automatic reference counting should avoid that.
The table view even displays the data but then immediately crashes with the EXC_BAD_ACCESS exception. I tried to set a general breakpoint in the XCode's breakpoints tab but however I don't get a reasonable error message on why the app crashes.
Do you have any ideas on why this happens. How can I get a better error message?
Thanks for your help in advance.
First of all check this function:
func carsLoaded(loadedCars: [Car])
It is returned as callback - what thread is it running? main or backround?
You should call on main thread
dispatch_async(dispatch_get_main_queue(), {
performSegueWithIdentifier("showAvailableCars", sender: self)
})
If doesn't help - provide the line, where it breaks in debugger, so I can help and see more.
UPD: Didn't notice, why do you do manual present?
presentViewController(carTableViewController, animated: false, completion: nil)
Your segue automatically shows view controller, you can change it's style (modal, push) in storyboard.
There are two potential issues that I can see. The first is that you are triggering your segue from a method that might not be on the main thread, you need to ensure that this is done on the main thread. The other issue is that in your prepare for segue you are unwrapping the new ViewController without checking it, so try this instead:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showAvailableCars" {
if let carTableViewController = segue.destinationViewController as? CarTableViewController {
carTableViewController.cars = self.cars
presentViewController(carTableViewController, animated: false, completion: nil)
}
}
}
As you said the table view displays the data and then immediately crashes I would assume that the problem is in your CarTableViewController. One thing you could check is when your cells are rendered, if you are trying to access some information that is null from the Car objects