206

I'm creating an app that uses the Facebook SDK to authenticate users. I'm trying to consolidate the facebook logic in a separate class. Here is the code (stripped for simplicity):

import Foundation

class FBManager {
    class func fbSessionStateChane(fbSession:FBSession!, fbSessionState:FBSessionState, error:NSError?){
        //... handling all session states
        FBRequestConnection.startForMeWithCompletionHandler { (conn: FBRequestConnection!, result: AnyObject!, error: NSError!) -> Void in
        
            println("Logged in user: \n\(result)");
        
            let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
            let loggedInView: UserViewController = storyboard.instantiateViewControllerWithIdentifier("loggedInView") as UserViewController
        
            loggedInView.result = result;
        
            //todo: segue to the next view???
        }
    }
}

I'm using the above class method to check session state changes, and it works fine.

Q: Once I have the user's data, how can I segue to the next view from within this custom class?

Just to be clear, I have a segue with identifier on the storyboard, and I'm trying to find a way to perform a segue from a class which is not the view controller

pkamb
  • 33,281
  • 23
  • 160
  • 191
Shlomi Schwartz
  • 8,693
  • 29
  • 109
  • 186

11 Answers11

366

If your segue exists in the storyboard with a segue identifier between your two views, you can just call it programmatically using:

performSegue(withIdentifier: "mySegueID", sender: nil)

For older versions:

performSegueWithIdentifier("mySegueID", sender: nil)

You could also do:

presentViewController(nextViewController, animated: true, completion: nil)

Or if you are in a Navigation controller:

self.navigationController?.pushViewController(nextViewController, animated: true)
Ogre Codes
  • 18,693
  • 1
  • 17
  • 24
Jérôme
  • 8,016
  • 4
  • 29
  • 35
  • 7
    Thanks for the reply, how can I call performSegueWithIdentifier from a custom class which is not the view? – Shlomi Schwartz Dec 23 '14 at 08:15
  • 2
    If i use your second method. How to pass data to the next view? – iamprem Apr 12 '15 at 20:23
  • 2
    You would use func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) and set the properties. – Lloyd Sargent May 01 '15 at 17:36
  • 1
    Note when calling performSequeWithIdentifer(), an implementation of prepareForSeque() in your calling controller will be called and you can do various data passing there - in fact, the sender parameter received in prepareForSeque() is simply what is passed to the sender parameter in performSequeWithIdentifier() – timbo Nov 04 '15 at 01:55
  • getting a non nil layout exception – Chris Hayes Mar 27 '16 at 00:22
  • 1
    What if the segue does not exist in the storyboard? For example, if you want to create a segue to ViewController itself? – scaryguy Aug 14 '16 at 10:53
  • I think after Mohit's edit the answer has became confusing. Assuming that `mySegueID` is pointing to some viewController *inside* StoryBoard...it would work with or without having a class in its *identity inspector*.If it's not pointing to a class then the viewController would be *not* having any specific functionality other than what you see in the storyboard itself. If you give it a class then it would benefit from what ever functionality the class has inside it. **But** if you just do `presentViewController` or `pushViewController` WITHOUT pointing to any viewController's storyboard ID – mfaani Sep 17 '16 at 14:07
  • then you **won't get anything from storyboard**, you would just get what's written in the code. So if you want to have **both** ie functionality of written code + views & outlets from your storyboard then you must do something like the answer in this [link](http://stackoverflow.com/a/24036067/5175709) ie you *MUST* 1) link viewController to class in storyboard 2) use **instantiateViewControllerWithIdentifier** to be able to get whatever you did in storyboard and (since you already did step 1 you would also have functionality from code) 3) **then** do your push or present. – mfaani Sep 17 '16 at 14:08
  • @scaryguy see my comments @@Jerome, I didn't make the edit myself, because I thought it would get rejected because of deviation from original author ... so please edit if you agree – mfaani Sep 17 '16 at 14:11
  • @iamprem We can use method `optional public func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool)` of the Navigation Controller you are pushing another UIViewController on. Note the Navigation Controller needs to be a delegate to invoke that method, meaning inside the Navigation Controller's vieDidLoad() we need to write: self.delegate = self. – Maxim Oct 30 '16 at 16:44
  • What in the world is nextViewController ?? I'm getting unresolved identifier – etayluz Nov 07 '16 at 00:39
  • Perfect! This fixed my issue! 1+ – Bishan Jan 11 '17 at 05:18
  • If I am using storyboards, is it better practice to create a segue and use performSegueWithIdentifier or just use presentViewController? – Ryan Jun 01 '19 at 19:06
20

If your segue exists in the storyboard with a segue identifier between your two views, you can just call it programmatically using

self.performSegueWithIdentifier("yourIdentifierInStoryboard", sender: self)

If you are in Navigation controller

let viewController = YourViewController(nibName: "YourViewController", bundle: nil)        
self.navigationController?.pushViewController(viewController, animated: true)

I will recommend you for second approach using navigation controller.

Manish Mahajan
  • 2,062
  • 1
  • 13
  • 19
18

You can use NSNotification

Add a post method in your custom class:

NSNotificationCenter.defaultCenter().postNotificationName("NotificationIdentifier", object: nil)

Add an observer in your ViewController:

NSNotificationCenter.defaultCenter().addObserver(self, selector: "methodOFReceivedNotication:", name:"NotificationIdentifier", object: nil)

Add function in you ViewController:

func methodOFReceivedNotication(notification: NSNotification){
    self.performSegueWithIdentifier("yourIdentifierInStoryboard", sender: self)
}
Rodrigo Otero
  • 222
  • 1
  • 9
  • 8
    Please do not abuse notifications like this. Use a delegate and make the connection between the view controller and your class explicit. It is near-impossible to observe the control flow of a notification, because there could be multiple subscribers, and instances can subscribe/unsubscribe at will. – uliwitness Nov 10 '19 at 09:57
16

You can use segue like this:

self.performSegueWithIdentifier("push", sender: self)
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
    if segue.identifier == "push" {

    }
}
DevB2F
  • 4,674
  • 4
  • 36
  • 60
Rameshwar Gupta
  • 791
  • 7
  • 21
13

Swift 3 - Also works with SpriteKit

You can use NSNotification.

Example:

1.) Create a segue in the storyboard and name the identifier "segue"

2.) Create a function in the ViewController you are segueing from.

func goToDifferentView() {

    self.performSegue(withIdentifier: "segue", sender: self)

}

3.) In the ViewDidLoad() of your ViewController you are segueing from create the observer.

NotificationCenter.default.addObserver(self, selector: #selector(goToDifferentView), name: "segue" as NSNotification.Name, object: nil)

Update - Last time I used this I had to change the .addObserver call to the following code to silence the errors.

NotificationCenter.default.addObserver(self, selector: #selector(goToDifferentView), name: NSNotification.Name(rawValue: "segue"), object: nil)

4.) In the ViewController or Scene you are segueing to, add the Post Method wherever you want the segue to be triggered.

NotificationCenter.default.post(name: "segue" as NSNotification.Name, object: nil)

Update - Last time I used this I had to change the .post call to the following code to silence the errors.

NotificationCenter.default.post(NSNotification(name: NSNotification.Name(rawValue: "segue"), object: nil) as Notification)
Timmy Sorensen
  • 570
  • 6
  • 16
  • Swift 3: `NotificationCenter.default.addObserver(self, selector: #selector(goToDifferentView), name: NSNotification.Name(rawValue: "segue"), object: nil)` – Michael Samoylov Sep 21 '16 at 16:40
5

There are already great answers above, i'd like to put little focus on preparation before performing segue.

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.destination is YourDestinationVC {
            let vc = segue.destination as? YourDestinationVC
            // "label" and "friends" are part of destinationVC
            vc?.label = "someText"
            vc?.friends = ["John","Mike","Garry"]
        }

Once your are done with data that you want to pass on to your destinationVC then perform your segue at an appropriate place. You can set "IdentifierOfDestinationVC" in StoryBoard Identity inspector in StoryBoard ID field

performSegue(withIdentifier: "IdentifierOfDestinationVC", sender: nil)
Dharman
  • 30,962
  • 25
  • 85
  • 135
Creeky Crap
  • 71
  • 1
  • 5
3

What you want to do is really important for unit testing. Basically you need to create a small local function in the view controller. Name the function anything, just include the performSegueWithIndentifier.

func localFunc() {
    println("we asked you to do it")
    performSegueWithIdentifier("doIt", sender: self)
}

Next change your utility class FBManager to include an initializer that takes an argument of a function and a variable to hold the ViewController's function that performs the segue.

public class UtilClass {

    var yourFunction : () -> ()

    init (someFunction: () -> ()) {
        self.yourFunction = someFunction
        println("initialized UtilClass")
    }

    public convenience init() {
        func dummyLog () -> () {
            println("no action passed")
        }
        self.init(dummyLog)
    }

    public func doThatThing() -> () {
        // the facebook login function
        println("now execute passed function")
        self.yourFunction()
        println("did that thing")
    }
}

(The convenience init allows you to use this in unit testing without executing the segue.)

Finally, where you have //todo: segue to the next view???, put something along the lines of:

self.yourFunction()

In your unit tests, you can simply invoke it as:

let f = UtilClass()
f.doThatThing()

where doThatThing is your fbsessionstatechange and UtilClass is FBManager.

For your actual code, just pass localFunc (no parenthesis) to the FBManager class.

pableiros
  • 14,932
  • 12
  • 99
  • 105
Bill
  • 1,588
  • 12
  • 21
3

This worked for me.

First of all give the view controller in your storyboard a Storyboard ID inside the identity inspector. Then use the following example code (ensuring the class, storyboard name and story board ID match those that you are using):

let viewController:
UIViewController = UIStoryboard(
    name: "Main", bundle: nil
).instantiateViewControllerWithIdentifier("ViewController") as UIViewController
// .instantiatViewControllerWithIdentifier() returns AnyObject!
// this must be downcast to utilize it

self.presentViewController(viewController, animated: false, completion: nil)

For more details see http://sketchytech.blogspot.com/2012/11/instantiate-view-controller-using.html best wishes

John Moutafis
  • 22,254
  • 11
  • 68
  • 112
gxmad
  • 1,650
  • 4
  • 23
  • 29
2

Another option is to use modal segue

STEP 1: Go to the storyboard, and give the View Controller a Storyboard ID. You can find where to change the storyboard ID in the Identity Inspector on the right. Lets call the storyboard ID ModalViewController

STEP 2: Open up the 'sender' view controller (let's call it ViewController) and add this code to it

public class ViewController {
  override func viewDidLoad() {
    showModalView()
  }

  func showModalView() {
    if let mvc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ModalViewController") as? ModalViewController {
      self.present(mvc, animated: true, completion: nil)
    }
  }
}

Note that the View Controller we want to open is also called ModalViewController

STEP 3: To close ModalViewController, add this to it

public class ModalViewController {
   @IBAction func closeThisViewController(_ sender: Any?) {
      self.presentingViewController?.dismiss(animated: true, completion: nil)
   }
}
aphoe
  • 2,586
  • 1
  • 27
  • 31
1

You can do this thing using performSegueWithIdentifier function.

Screenshot

Syntax :

func performSegueWithIdentifier(identifier: String, sender: AnyObject?)

Example :

 performSegueWithIdentifier("homeScreenVC", sender: nil)
Jayprakash Dubey
  • 35,723
  • 18
  • 170
  • 177
0

This worked for me:

//Button method example
 @IBAction func LogOutPressed(_ sender: UIBarButtonItem) {

        do {
            try Auth.auth().signOut()
            navigationController?.popToRootViewController(animated: true)

        } catch let signOutError as NSError {
          print ("Error signing out: %@", signOutError)
        }


    }