I'm trying to make call a method from another class when a button is pressed, but it is not working and giving a fatal error (unexpectedly found nil while unwrapping an Optional value). I don't know if I'm doing something wrong.
Here's the code from the first class (FirstViewController)
#IBAction func pressButton(sender: UIButton) {
SecondViewController().pressedButton()
}
Here's the code in another class (SecondViewController)
func pressedButton() {
self.scrollView.setContentOffset(CGPoint(x: 320, y: 0), animated: true)
}
I placed the above code right after viewDidLoad (Within SecondViewController's class).
The problem is that in your first code SecondViewController() creates a whole new SecondViewController instance. That is not the same SecondViewController that has a scrollView, so its scrollView is nil and you crash when you try to use it.
Instead, a SecondViewController needs to exist already in your view controller hierarchy, and you need to talk to that SecondViewController.
Related
This is my StartViewController with simple #IBAction:
class StartViewController: UIViewController {
#IBAction func startButtonTapped(sender: UIButton) {
revealViewController().revealToggleAnimated(true) //error
}
}
Along with the commented line there is an error:
fatal error: unexpectedly found nil while unwrapping an Optional value
What am I doing wrong?
Of course StartViewController is presented from SWRevealViewController via sw_front segue.
For some reason revealViewController() returns nil here. Why?
This is how my storyboard looks like:
When you implementing SWRevealViewController using storyboard, you should set Storyboard Entry Point to SWRevealViewController instance (Reveal View Controller on picture), not to front view controller (Start View Controller on picture).
Note that revealViewController method return optional pointer, and even when all is set properly it return nil until view is loaded, so you better use optional chaining:
revealViewController()?.revealToggleAnimated(true)
I think your app might crash because you are missing a delegate.Try to add the following to your protocols:
class StartViewController: UIViewController,SWRevealViewControllerDelegate {
#IBAction func startButtonTapped(sender: UIButton) {
revealViewController().revealToggleAnimated(true) //error
}
}
If it still crashes, try changing the revealViewController().revealToggleAnimated(true) with revealViewController().revealToggle(self) and see if it still crashes.
I have tried several different answers and have yet to find an answer that works.
I keep getting
fatal error: unexpectedly found nil while unwrapping an Optional value
I have a SKScene called selectUpgrade that is in my GameViewController and I am trying to segue to a UIViewController called MissileUpgrade.
var vc2 = MissileUpgrade() //trying to get to this UIViewController
var gameVC = GameViewController() // currently in a scene in this UIViewController
I am calling this in my scene to segue
func goToMissileUpgrade() {
gameVC.presentViewController(vc2, animated: true, completion: nil)
}
These are in the same storyboard. If I set MissileUpgrade as the initial VC it will load fine so I know it has nothing to do on that end. I am lost on why this is not working. Thanks for your help!
If you are using storyboards, don't use presentViewController use performSegueWithIdentifier
Give your Segue an Identifier in the Storyboard, then refer to it in the code like so:
self.performSegueWithIdentifier("yourSegueIdentifierHere", sender: self)
Let's say I have a firstViewController and a secondViewController. The first one contains a firstButton and the second one - a secondButton. Here's what I want to do: when user clicks the secondButton, some firstButton's property changes.
Unfortunately, when I create an instance of a firstViewController in a secondViewController and then trying to access a firstButton, I get an error:
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
So, technically, I'm trying to do this as follows:
var ins = firstViewController()
#IBAction func secondButtonisPressed(){
ins.firstButton.alpha = 0
}
What is the proper way to implement that?
Thanks in advance.
Your problem here is that the IBOutlets of your firstViewController are only available (!= nil) after the viewDidLoad() firstViewController's method has being called.
In other words, you have to present the view, before you can make any changes to a UIViewController IBOutlet.
How you can solve this?
Add a variable into FirstViewController that works as a flag for you.
for example: var hideFirstButton = false
in the viewDidLoad or viewWillAppear method of FirstViewController check for hideFirstButton's value and hide or show your firstButton.
Then, before you present your FirstViewController change the value of hideFirstButton to the needed for your application to run fine.
UPDATE:
Other workaround, using Storyboard is (This approach has the inconvenient that the completion handler is called after viewWillAppear() so the button is visible for a second):
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let firstViewController = storyboard.instantiateViewControllerWithIdentifier("FirstViewController") as! FirstViewController
self.presentViewController(firstViewController, animated: true, completion: {
//This lines will be called after the view is loaded so the code will run
firstViewController.firstButton.alpha = 0
})
EXAMPLE: an example at GitHub
You could try to do this using delegation, similar to the way Apple does it in their existing frameworks. For an example, look at the way that you use UITableViewDelegate when working with a UITableView object.
If you wanted to use delegation to tell secondViewController that firstButton was pressed using delegation, you could do it as follows:
Step 1:
Create a protocol containing a method for the button press event.
protocol buttonPressDelegate {
func buttonPressed() -> Void
}
Step 2:
In firstViewController, declare that you have an instance of an object of type buttonPressProtocol.
var buttonPressDelegateObj: buttonPressDelegate?
Step 3:
In firstViewController, initialize your buttonPressDelegateObj to contain a reference to your instance of secondViewController. If you want you can create a method to set the reference contained in buttonPressDelegateObj, or do it viewDidLoad in firstViewController, etc.
buttonPressDelegateObj = secondViewControllerObj
Step 4:
In secondViewController, declare that you adopt the buttonPressDelegate protocol.
class secondViewController: UIViewController, buttonPressDelegate {
Step 5:
In secondViewController, implement the protocol method buttonPressed() by adding the function with your desired implementation. Here's an example:
func buttonPressed() {
secondButton.alpha = 0
}
Step 6:
Create an #IBAction on the button in firstViewController, so that when the button is pressed it calls buttonPressDelegateObj.buttonPressed() and you can respond to the event
#IBAction func firstButtonPressed() {
if (buttonPressDelegateObj != nil) {
buttonPressDelegateObj.buttonPressed()
}
else {
print("You forgot to set your reference in buttonPressDelegateObj to contain an instance of secondViewController!")
}
}
Note: This is just one way that you could do this. To tell firstViewController that secondButton was pressed (go the other way), have firstViewController implement the protocol buttonPressDelegate, have secondViewController contain a reference to firstViewController as an instance of type buttonPressDelegate?, and create an #IBAction in secondViewController that fires when secondButton is pressed that calls your the buttonPressDelegate method.
Note: There is a similar pattern employed in the Android world to get a Fragment to communicate to an Activity, that you can read more about here
I tried this solution but I get an error AnyObject is not convertible to 'UIViewController' did you mean to use 'as!' to force downcast?
What am I doing wrong?
I call the following code in LoginViewController viewDidLoad, I check if the user has logged in yet if yes then I want to skip the login view and go directly to Latest Photos which is FirstViewController...
override func viewDidLoad() {
super.viewDidLoad()
if(defaults.objectForKey("loggedIn") != nil){
let vc : UIViewController = self.storyboard?.instantiateViewControllerWithIdentifier("LatestPhotosView") as! UIViewController;
self.presentViewController(vc, animated: true, completion: nil)
}
}
Since Swift 1.2, you have to cast explicitly, change your as to as!.
This indicates to the programmer that the cast can fail. This decreases the probability of errors in your code.
Edit:
You have to call this in your viewDidAppear: method, as this will fail on your viewDidLoad() (Your view doesn't exist yet)
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)
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.
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:
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