40

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?

Here is the code for the LoginViewController Here is the attributes panel for the KlikurTableViewController

Trenton Tyler
  • 1,692
  • 3
  • 24
  • 53
  • Remove the segue and on button click event perform the navigation. – Yogesh Suthar Aug 30 '15 at 02:32
  • That code is in the fuction buttonTapped so it should transition, but its not. – Trenton Tyler Aug 30 '15 at 02:36
  • Have you removed the segue from storyboard? – Yogesh Suthar Aug 30 '15 at 02:42
  • Yes there is no segue linking the login to the sucessfulLoginPage – Trenton Tyler Aug 30 '15 at 02:56
  • Heh, I found my issue like this with debug what Segues I have: po self.perform(NSSelectorFromString("storyboardSegueTemplates")). Also, I found issue, there is no segues after previous UIViewController added in UINavigationStack by code not by performSegue :)), I fix it by performSegue for previous controller :) – iTux Aug 13 '18 at 18:27

8 Answers8

75

[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

Naveed Ahmad
  • 6,627
  • 2
  • 58
  • 83
BaseZen
  • 8,650
  • 3
  • 35
  • 47
  • Not helpful unless you reproduce the crash message exactly here. – BaseZen Aug 30 '15 at 03:01
  • 1
    2015-08-29 22:07:26.879 ParseStarterProject-Swift[6339:925415] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Receiver () has no segue with identifier 'test'' – Trenton Tyler Aug 30 '15 at 03:07
  • I was running into that same problem, I call performSegueWithIdentifier(), prepareForSegue() is being called and .... NOTHING.... Making sure that performSegueWithIdentifier() runs on the main thread solved my problem... – user2962499 Oct 21 '15 at 22:22
  • 2
    You sir are amazing I've been struggling with this for weeks, this worked perfectly! I would have never guessed the main queue as the issue. – Joseph Astrahan Mar 28 '16 at 02:00
  • 1
    I also got an occasional crash when using this code. In my case, "self" was pointing to UIViewController, which has been apparently deallocated (probably because a user have tapped on Bak button) by the time the asynchronous callback was called. As a solution, I've changed "unowned" to "weak" and call self?.performSegueWithIdentifier(...) – Crulex May 27 '16 at 12:54
  • @Crulex I'm surprised no one has pointed this out before. This is a hole in my solution. Your diagnosis is exactly correct. – BaseZen May 27 '16 at 23:56
18

Make sure you're putting your: self.performSegue(withIdentifier: ..., ...)

in viewDidAppear or later. It won't work in viewWillAppear or viewDidLoad.

GregJaskiewicz
  • 456
  • 3
  • 11
17

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.

Mette
  • 1,166
  • 11
  • 12
8

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: enter image description here

NRitH
  • 13,441
  • 4
  • 41
  • 44
  • Can you post a screenshot of the view controllers and segue in the storyboard, and the attributes panel for the segue? – NRitH Aug 30 '15 at 03:44
  • They have been added. – Trenton Tyler Aug 30 '15 at 03:53
  • The properties that you have in the second screenshot are for a *view controller* named "Klikur". I need to see a *segue* that has an ID of "Klikur". Space your view controllers out a bit further apart in the storyboard, and you'll see the segues. There should be one from your login scene to your success scene, and its ID should be "Klikur". – NRitH Aug 30 '15 at 05:01
7

swift 3.x

DispatchQueue.main.async(){
    self.performSegue(withIdentifier: "Klikur", sender: self)
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
Felecia Genet
  • 367
  • 4
  • 3
5
DispatchQueue.main.async() {
    self.performSegue(withIdentifier: "GoToHomeFromSplash", sender: self)`  
}
Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
ibk
  • 51
  • 1
  • 1
  • Add some comments also with your code to make people understand what you are trying to convey. – Nipun Aug 02 '17 at 12:18
1

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.

Justin Domnitz
  • 3,217
  • 27
  • 34
1

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)
}
Ppillo
  • 31
  • 3