In my main storyboard I have 1 Navigation controller and subsequent swift file NavigationCtr.swift.
The viewController is in different xib.
Now I want to push my viewController from the NavigationCtr class.
let vcFirst = FirstViewController(nibName: "FirstViewController", bundle: nil)
self.navigationController!.pushViewController(vcFirst, animated: true)
I am getting a exception
fatal error: unexpectedly found nil while unwrapping an Optional value
So when I trying to print from viewDidLoad
print(self.navigationController)
in NavigationCtr.swift class it is giving nil. So nothing works
I created a new project in objective C and it works fine.
Attached the storyboard image
Any hint in the direct direction is highly appreciated.
Note : - I am new to swift
the main issue was using the self.navigationController
As my storyboard only had navigationController I should just self keyword.
as below
let vcFirst = FirstViewController(nibName: "FirstViewController", bundle: nil)
self.pushViewController(vcFirst, animated: true)
For the first controller, set it as rootController instead of push (show).
self.navigationController = UINavigationController(rootViewController: vcFirst)
Or set its view controllers
self.navigationController = UINavigationController()
navigationController.setViewControllers([vcfirst], animated: true)
Related
I am facing a serious issue. I have a framework named "abc.framework" and it contains a function in which you can pass a viewController object.
//this is the method in my framework
func launchView(view:UIViewController){
}
//this is what needs to be written on App side
myFrameworkFile.launchView(view: self)
The above method is used to present the Framework's viewcontroller i.e abc.framework viewController on top of the the viewController object passed. The launch method contains the following code :
func launchView(view:UIViewController)
{
var rootController: UIViewController?
rootController = view
let storyboard = UIStoryboard(name: "abcFrameworkBundle.bundle/abcUIMain", bundle: nil)
let VC1 = storyboard.instantiateViewController(withIdentifier: "selectedviewcontroller") as! SelectedViewController
rootController?.present(VC1, animated: true, completion: nil)
}
From the above method the viewController the SelectedViewController viewDidLoad method is getting called however it is not getting presented and I am getting the below warning
Warning: Attempt to present on
whose view is not in the window hierarchy!
Has anyone faced the same issue?
I guess you call this method myFrameworkFile.launchView(view: self) in viewDidLoad or earlier. Try to move it inside viewDidAppear
Beginner swift-user here. I want to open a pop up connected to a seperate view controller (P2_Gift_Pop_Up) from the main view controller. To this end I include the following in a code snippet in my main view controller
let vc = P2_Gift_Pop_Up()
vc.modalPresentationStyle = .overCurrentContext
present(vc, animated: true, completion: nil)
This starts running code in the popup window (a print statement works anyway), so so far, so good.
However, when I try to modify some elements in the view connected to the view controller I get a
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an
Optional value. Here is the code in its entirety.
import UIKit
class P2_Gift_Pop_Up: UIViewController {
#IBOutlet weak var Slot1: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
Slot1.setImage(UIImage(named: "Card 2 Red"), for: .normal)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Although I have (via another answer on this site) an understanding of what the msg means, I don't understand why I get it in this context, and how to fix it. It might also bear emphasis that although code starts running after the P2_Gift_Pop_Up call, the corresponding view is not shown.
You need to present your controller like this(you need to set your storyboard ID and then add this in identifier):
let storyBoard = UIStoryboard(name: "main", bundle: Bundle.main)
let vc = storyBoard.instantiateViewController(withIdentifier: "your storyboard id") as! P2_Gift_Pop_Up
vc.modalPresentationStyle = .overCurrentContext
present(vc, animated: true, completion: nil)
Your app is crashing because of your button(Slot1) not have a memory so you need to present like above.
#JogendarChoudhary told you what to do, but didn't really explain why.
When you create your P2_Gift_Pop_Up with
let vc = P2_Gift_Pop_Up()
You aren't initializing it properly. It doesn't get a chance to load it's views from it's XIB file/storyboard.
Assuming you have your view controller defined in your main app storyboard, you need to load the view controller from the storyboard.
You should add a unique identifier to your view controller in your storyboard, and then load it using that identifier. (Using the name of the class as the identifier is as good a choice as any.)
The UIViewController class has a property storyboard which will contain the storyboard from which it's loaded. Usually that's your app's main storyboard, and what you want. Thus:
if let vc = storyboard?.instantiateViewController(withIdentifier: "P2_Gift_Pop_Up id")
as? P2_Gift_Pop_Up {
vc.modalPresentationStyle = .overCurrentContext
present(vc, animated: true, completion: nil)
} else {
print("error creating P2_Gift_Pop_Up")
}
In my iOS app I would like that, after a positive result, the screen to switch back to the previous screen. In my case I would switch back from a UITableViewController to another UITableViewController. I have tried with:
self.dismissViewControllerAnimated(true, completion:nil)
but it doesn't work, nothing appears.
I have also tried with:
let mainStoryboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
let vc : UITableViewController = mainStoryboard.instantiateViewControllerWithIdentifier("Tasks") as UITableViewController
self.presentViewController(vc, animated: true, completion: nil)
but I get "fatal error: unexpectedly found nil while unwrapping an Optional value" in the method viewWillAppear() in:
self.navigationController!.editButtonItem().title = "Modifica"
I with I could avoid add another reverse segue from the interface builder and then perform performSegueWithIdentifier(). Isn't there a faster way?
Are you using a UINavigationController? If so, you just call navigationController?.popViewControllerAnimated(true) to go to the previous screen.
If you aren't using a UINavigationController, you can call dismissViewControllerAnimated(true, completion: nil) to achieve the same result.
In both cases, you don't need to use self as it is already implied.
I am really struggling with what should be a simple bit of code.
I have an ios app that has 4 tabs in the uitabcontroller, depending on the a setting in another tab I wanted to replace the controller the first tab goes to. I found that I couldnt simply replace this first tab (although i managed it somehow for a couple of builds then it stopped working after a clean).
So I went for the option of replacing the viewcontrollers that the tab controller references with the .setViewControllers method. I call this from my viewcontroller after the viewdidload method.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc0 = storyboard.instantiateViewControllerWithIdentifier("collection")
let vc4 = storyboard.instantiateViewControllerWithIdentifier("profilenews1")
let vc1: UIViewController! = storyboard.instantiateViewControllerWithIdentifier("news") as UIViewController
let vc2: UIViewController! = storyboard.instantiateViewControllerWithIdentifier("create") as UIViewController
let vc3: UIViewController! = storyboard.instantiateViewControllerWithIdentifier("search") as UIViewController
let controllers = [vc0,vc4]
self.tabBarController?.setViewControllers(controllers, animated: true)
From the research I have done this should work, setViewControllers is documented and I have seen numerous objective c examples but I get EXEC_BAD_INSTRUCTION thrown.
I have checked that the tabcontroller is correct before I replace by doing
println("number of navs: \(self.tabBarController?.viewControllers?.count)")
And that prints the correct number of controllers.
Any ideas?
I am trying to add some unit tests to my project to test view controllers. However I seem to be having problems with seemingly simple things. I have created a sample project which I will refer to. https://github.com/pangers/ViewControllerTesting
The sample contains a UINavigationController as the initial view controller. The root view controller of the UINavigationController is FirstViewController. There is a button on FirstViewController that segues to SecondViewController. In SecondViewController there is an empty textfield.
The two tests I am trying to add are:
1) Check button title in FirstViewController is "Next Screen".
2) Check textfield in SecondViewController is empty, "".
I have heard reports of adding your swift files to both the main target and the test target is not good practice. But rather it is better to make whatever you want to access in your tests public and import the main target into the tests. So that is what I have done. (I have also set the "Defines Module" for the main target to YES as that is what I have read in a few articles aswell).
In FirstViewControllerTests I have instantiated the first view controller with the following:
var viewController: FirstViewController!
override func setUp() {
let storyboard = UIStoryboard(name: "Main", bundle: NSBundle(forClass: self.dynamicType))
let navigationController = storyboard.instantiateInitialViewController() as UINavigationController
viewController = navigationController.topViewController as FirstViewController
viewController.viewDidLoad()
}
And I have added the test:
func testCheckButtonHasTextNextScreen() {
XCTAssertEqual(viewController.button.currentTitle!, "Next Screen", "Button should say Next Screen")
}
Similarly, for SecondViewControllerTest, I have set it up using:
var secondViewController:SecondViewController!
override func setUp() {
let storyboard = UIStoryboard(name: "Main", bundle: NSBundle(forClass: self.dynamicType))
let navigationController = storyboard.instantiateInitialViewController() as UINavigationController
let firstviewController = navigationController.topViewController as FirstViewController
firstviewController.performSegueWithIdentifier("FirstToSecond", sender: nil)
secondViewController = navigationController.topViewController as SecondViewController
secondViewController.viewDidLoad()
}
And the test:
func testTextFieldIsBlank() {
XCTAssertEqual(secondViewController.textField.text, "", "Nothing in textfield")
}
They both fail and I am not too sure as to why. My suspicion is that the way I am instantiating the view controllers is not correct. Is the best way to instantiate the view controllers is to use the storyboard (just like it would if it were to run in real life)? Or is it acceptable to be instantiated via:
var viewController = FirstViewController()
What are you guys' experience with TDD and view controllers in swift?
I am using Swift with XCode 6.1.1.
Thanks in advance.
Solved
Ok after considering the answers from modocache and Mike Taverne, I've found my solution and I've learnt a few things which I will write down below.
1) I made anything class/method/variable that I want to test public. I do not need to add the swift files to the test target.
2) I only needed to set "Defines Module" for the "Main" target (as opposed to the "Test" target or the entire project)
3) When instantiating the storyboard, the bundle should be set to nil rather than NSBundle(forClass: self.dynamicType), otherwise tests will fail.
4) As modocache stated, it is good to give your view controller's a StoryboardID and instantiate them like so:
viewController = storyboard.instantiateViewControllerWithIdentifier("FirstViewController") as FirstViewController
However, instantiating the view controller like this ONLY instantiates the view controller alone, and not any navigation controllers that it may be embedded in. That means, attempting to do
XCTAssertFalse(viewController.navigationController!.navigationBarHidden, "Bar should show by default")
will result in a nil exception. I confirmed this with
XCTAssertNil(viewController.navigationController?, "navigation controller doesn't exist")
which resulted in a successful test.
Since I wanted to check the state of the navigation bar in FirstViewController, you must instantiate the view controller like so:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navigationController = storyboard.instantiateInitialViewController() as UINavigationController
viewController = navigationController.topViewController as FirstViewController
Now performing the test
XCTAssertFalse(viewController.navigationController!.navigationBarHidden, "nav bar should be showing by default")
results in a successful test.
5) let _ = viewController.view does indeed trigger viewDidLoad() which was confirmed by a test
6) let _ = viewController.view does not trigger viewWillAppear(), and I presume anything afterwards aswell. viewController.viewWillAppear(false/true) needs to be called manually to trigger it (Confirmed by a test).
Hopefully this will be of help to people. I will push the updated project to GitHub (link above) if anyone would like to play around with it.
Update #2
After all the above, I still could not figure out how to transition from the first view controller to the second view controller (so that I may test navigation bar properties in SecondViewControllerTests.swift). I tried
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let nc = storyboard.instantiateInitialViewController() as UINavigationController
let firstVC = nc.topViewController as FirstViewController
firstVC.performSegueWithIdentifier("FirstToSecond", sender: nil)
secondVC = nc.topViewController as SecondViewController
which caused an error.
I also tried
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let nc = storyboard.instantiateInitialViewController() as UINavigationController
let firstVC = nc.topViewController as FirstViewController
firstVC.toSecondVCButton.sendActionsForControlEvents(UIControlEvents.TouchUpInside)
secondVC = nc.topViewController as SecondViewController
which did not work.
I eventually tried
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let nc = storyboard.instantiateInitialViewController() as UINavigationController
vc = storyboard.instantiateViewControllerWithIdentifier("Second") as SecondViewController
nc.pushViewController(vc, animated: false)
let _ = vc.view
vc.viewWillAppear(false)
which worked perfectly with my tests (allowed me to access navigation bar properties)!
I agree with #MikeTaverne's answer: I prefer accessing -[UIViewController view] in order to trigger -[UIViewController viewDidLoad], rather than calling it directly. See if the test failures for FirstViewController go away once you use this instead:
viewController = navigationController.topViewController as FirstViewController
let _ = viewController.view
I'd also recommend giving both view controllers identifiers in your storyboard. This will allow you to instantiate them directly, without accessing them via UINavigationController:
var secondViewController: SecondViewController!
override func setUp() {
let storyboard = UIStoryboard(name: "Main", bundle: NSBundle(forClass: self.dynamicType))
secondViewController = storyboard.instantiateViewControllerWithIdentifier("SecondViewController")
as SecondViewController
let _ = secondViewController.view
}
Check out my talk on testing UIViewController at Brooklyn Swift for details: https://vimeo.com/115671189#t=37m50s (my presentation begins around the 37'50" mark).
I've begun unit testing view controllers recently, and it poses some unique challenges.
One challenge is getting the view to load. Looking at your set up for FirstViewController, you are trying to do this with viewController.viewDidLoad().
My suggestion is to replace that line with this:
let dummy = viewController.view
Accessing the .view property will force the view to load. This will trigger the .viewDidLoad in your ViewController, so don't call that method explicitly in your test.
This approach is considered hacky by some people, but it is simple and effective. (See Clean way to force view to load subviews early)
As an aside, I am finding the best way to test view controllers is to move as much code out of the view controllers as possible into other classes that are more easily tested.
If your view controller is defined in a storyboard, then you need to instantiate it that way for your outlets to be set up properly. Trying to initialize it like an ordinary class won't work.