0

Short explanation.

I have a ContainerViewController that I'm pushing to the navigationStack.

The ContainerViewController has 2 child ViewControllers. A SlidePanelViewController (a slide-out menu) and a CenterViewController (the content)

I have a button in my menu to "sign Out". When this button is clicked I want to push ContainerViewController (and it's 2 childViewControllers) to my LandingPageViewController.

Here's the function I am trying to call:

func signOut() {
println("signOut")


// Set up the landing page as the main viewcontroller again.
let mainTableViewController = LandingPageVC()
mainTableViewController.navigationItem.setHidesBackButton(true, animated: false)
mainTableViewController.skipView = false
self.navigationController!.pushViewController(mainTableViewController, animated: true)

// Disable menu access
menuEnabled = false

// change status bar style back to default (black)
UIApplication.sharedApplication().statusBarStyle = UIStatusBarStyle.Default
}

At first I tried putting this in my SlidePanelViewController. That didn't work. So I put it where I'm assuming it belongs in the ContainerViewController.

However when I click my signOutButton in my menu. I'm presented with the error:

fatal error: unexpectedly found nil while unwrapping an Optional value

When looking into the error. This is the line causing it:

self.navigationController!.pushViewController(mainTableViewController, animated: true)

After the error I checked that the function works, by adding a UINavigationBarButtonItem that called the function (in my ContainerViewController). It did exactly what I wanted.

However when I call this function from my Menu (again my menu is a childViewController of the ContainerViewController). It does not work.

I'm attempting to call it like so:

ContainerViewController().signOut()

I also tried adding a Delegate to my SidePanelViewController like this:

Before the class:

@objc protocol SidePanelViewControllerDelegate {
    optional func needsSignOut(sender: SidePanelViewController)
    optional func toggleLeftPanel()
    optional func collapseSidePanels()
}

in viewDidLoad():

//  Make sure your delegate is weak because if a ContainerViewController owns
//  a reference to a SidePanelViewController and the container view controller
//  is its delegate, you'll end up with a strong reference cycle!
weak var delegate: SidePanelViewControllerDelegate?

in my tap gesture function:

func signOutTapGesture() {
    println("signOutTapGesture")
    selectView(signOutView)

    delegate?.needsSignOut?(self)
    println(delegate)
}

before my ContainerViewController class:

var leftViewController: SidePanelViewController?

my ContainerViewController class:

class ContainerViewController: UIViewController, CenterViewControllerDelegate, SidePanelViewControllerDelegate, UIGestureRecognizerDelegate {

in my ContainerViewController's viewDidLoad()

leftViewController?.delegate = self

And I changed the signOut function in the ContainerViewController class to this:

func needsSignOut(sender: SidePanelViewController) {
    println("needsSignOut called")
    self.signOut()
}

However using the delegate like above, doesn't seem to do anything either.

Any help as to How I can successfully push my LandingPageVC from the menu would be greatly appreciated! (I'm not using storyboards)

Mark L
  • 759
  • 2
  • 12
  • 29
  • I gave an answer to this Stackoverflow question which I think can help you architech your app: http://stackoverflow.com/questions/28640969/passing-view-controllers-with-facebook-login-confirmation/28669637#28669637 – Zhang Apr 04 '15 at 15:48

2 Answers2

1

You're attempting to call signOut with ContainerViewController().signOut(). This will create a new ContainerViewController and because you haven't pushed it onto the navigation controller's stack, navigationController is nil. Try just calling self.signOut(). (I'm assuming signOut in a method of ContainerViewController)

Update - delegates

Your delegate property should go in SidePanelViewController. I'll give you and example of how to implement it:

SidePanelViewController: (Note - the protocol doesn't have to go here but I think it keeps things organised)

@objc protocol SidePanelViewControllerDelegate {
    optional func needsSignOut(sender: SidePanelViewController)
}

class SidePanelViewController: UIViewController {
    //  Make sure your delegate is weak because if a ContainerViewController owns
    //  a reference to a SidePanelViewController and the container view controller
    //  is its delegate, you'll end up with a strong reference cycle!
    weak var delegate: SidePanelViewControllerDelegate?

    //  Called when the UIButton is pressed.
    func myButtonWasPressed() {
        delegate?.needsSignOut?(self)
    }
}

ContainerViewController:

class ContainerViewController: UIViewController {
    var sidePanel: SidePanelViewController!
    // Setup the side panel...

    override func viewDidLoad() {
        super.viewDidLoad()
        sidePanel.delegate = self
    }

    func signOut() {
        // Sign out stuff here.
    }
}

//  The ContainerViewController needs to conform to the SidePanelViewControllerDelegate
//  protocol if we want the delegate to work. (This could have gone in the initial
//  class declaration.)
extension ContainerViewController : SidePanelViewControllerDelegate {
    func needsSignOut(sender: SidePanelViewController) {
        self.signOut()
    }
}

Hope that helps.

ABakerSmith
  • 22,759
  • 9
  • 68
  • 78
  • Tried doing `self.signOut()` but it causes the following error: `'SidePanelViewController' does not have a member named 'signOut'` – Mark L Apr 04 '15 at 10:53
  • That error is because you're trying to call `signOut` from an instance of the `SidePanelViewController` which obviously doesn't contain your `signOut` method. Where exactly is the `signOut` method? – ABakerSmith Apr 04 '15 at 20:41
  • the `signOut` function is in the `ContainerViewController`. Hence why I was trying to call `ContainerViewController().signOut()` and not just `signOut()`. If you are asking where `signOut` is being called from? The button that needs to run that function is in my `SidePanelViewController`. – Mark L Apr 04 '15 at 22:56
  • Ahh okay, that makes sense. What I would suggest you do is make a `SidePanelViewControllerDelegate`, create a delegate property on your `SidePanelViewController` and set your containerViewController object as the delegate. Then when the button on the side panel is pressed, use the delegate to pass the message to the containerViewController, which can then call `signOut`. – ABakerSmith Apr 04 '15 at 23:07
  • Thanks for the reply! I have never worked with my own delegates before. But I will start reading into this right away! – Mark L Apr 04 '15 at 23:11
  • ABakerSmith I think I got a delegate set up. But calling it doesn't seem to do anything. I updated my original post with the code for my delegate in case I'm doing something wrong. – Mark L Apr 04 '15 at 23:17
  • 1
    I'll update my answer in response to the delegate bit. – ABakerSmith Apr 04 '15 at 23:23
  • Sorry for my abscense yesterday, been quite busy. Just had a chance to try out your updated answer. However despite having copy/pasted what you wrote. When I click my sign out button. It does not run the `needsSignOut` function. Tested how much of it worked with "println's" – Mark L Apr 06 '15 at 21:11
  • 1
    I would suggest using `println` to check `myButtonWasPressed` is being called. If if is being called you should use `println` to check `delegate` isn't `nil`. – ABakerSmith Apr 06 '15 at 21:16
  • My function is being called, but you're right. My delegate prints out as "nil" :(? – Mark L Apr 06 '15 at 21:20
  • 1
    Humm, very interesting... Well not being able to see your code makes this a bit harder. How are you setting up your `SidePanelViewController`. The problem is the delegate was somehow not set properly... – ABakerSmith Apr 06 '15 at 21:22
  • I simply copy pasted your example into my project. If there's supposed to be anymore code setting up the delegate. I'm missing that. I'll go ahead and edit my post with the updated code. – Mark L Apr 06 '15 at 21:23
  • Updated my post with the new code related to the delegate changes. – Mark L Apr 06 '15 at 21:28
  • 1
    Check, when you use `leftViewController?.delegate = self` that `leftViewController` isn't nil. That would cause the delegate to remain as nil. – ABakerSmith Apr 06 '15 at 21:32
  • you're right again. my leftViewController is nil, when I try to do `leftViewController?.delegate = self` But I'm declaring my variable before viewDidLoad()? Is this wrong? `var leftViewController: SidePanelViewController?` – Mark L Apr 06 '15 at 21:36
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/74587/discussion-between-mark-l-and-abakersmith). – Mark L Apr 06 '15 at 21:38
0

The problem seems to be that navigationController is nil and you're trying to force unwrap it (as indicated by your error).

One problem I discussed in my other answer.

Another problem may be you haven't added a navigation controller. To do this you need to:

If you're using Storyboards

You need to make sure you've embedded your UINavigationController. After that, when you use navigationController it won't be nil and you'll be able to push your view controller.

When you're on your storyboard:

enter image description here

Also, if you're using storyboards, have you considered using segues to move around instead of calling presentViewController? I've found it makes everything much easier.

If you're not using Storyboards

Have a look at this post: Programatically creating UINavigationController in iOS

Community
  • 1
  • 1
ABakerSmith
  • 22,759
  • 9
  • 68
  • 78
  • I'm not using storyboards. Hence pushing to the navigationStack rather than simply using segues. How do you embed in a navigation Controller programatically? – Mark L Apr 04 '15 at 10:28