UIApplication.sharedApplication().keyWindow is nil inside test of framework - frameworks

I am trying to test (using XCTest framework) view controllers that reside inside a framework (dynamic library). I am running into a problem with the property UIApplication.sharedApplication().keyWindow, which is nil. I have the following code:
override func setUp() {
super.setUp()
let storyboard = UIStoryboard(name: STORYBOARD_NAME_REPORT,
bundle: BundleUtil.getCurrentBundle())
let chatViewController = storyboard.instantiateViewControllerWithIdentifier(VIEW_CONTROLLER_ID_TIP_CHAT) as UIViewController
let navigationController = UINavigationController()
let viewControllers = [chatViewController]
navigationController.viewControllers = viewControllers
// PROBLEM: The following causes a crash because keyWindow is nil.
UIApplication.sharedApplication().keyWindow!.rootViewController = chatViewController
// Force the view controller to be loaded.
// Source: https://www.natashatherobot.com/ios-testing-view-controllers-swift/
let _ = navigationController.view
let _ = chatViewController.view
}
Any ideas how to make the keywindow property initialize properly?

Can you try setting it like below:
Get the shared window first and assign the view controller as root controller
var window = UIApplication.shared.delegate?.window!
window.rootViewController = viewController
and then make the window key and visible.
window.makeKeyAndVisible()
CATransaction.flush() //optional

Related

How to pass data to another NSViewController before viewdidload of that controller is called

Whenever I load another window with its view controller, I am trying to pass some data to it. That data need to be used during viewdidload is called but by the time viewdidload is called value is not assigned. I need a way where we can pass data between view controllers of two different windows before viewdidload of second controller is called.
My Sample code:
{
let storyboardName = NSStoryboard.Name(stringLiteral: "Main")
let storyboard = NSStoryboard(name: storyboardName, bundle: nil)
let storyboardID = NSStoryboard.SceneIdentifier(stringLiteral: "MainWindowController")
if let chooseWindowController = storyboard.instantiateController(withIdentifier: storyboardID) as? NSWindowController {
if let chooseVC = chooseWindowController.contentViewController as? ViewController {
chooseVC.buttonAction = "NewDocument"
}
chooseWindowController.showWindow(nil)
}

How can I set a VC as Liveview in Playgrounds?

I'm currently working on a small Playgrounds project (for macOS) and I'm trying to set my own View Controller as the Live View. The following line doesn't work.
PlaygroundPage.current.liveView = ViewController()
When running this, I get the following error.
error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).
I get the same error, when using this:
PlaygroundPage.current.liveView = ViewController(nibName: NSNib.Name("MyView"), bundle: Bundle.main)
Choose File > New > Playground and start with the MacOS Playground template called Single View. This will give you a view in a nib.
Now modify the code in the playground as follows:
import AppKit
import PlaygroundSupport
class ViewController : NSViewController {}
let nibFile = NSNib.Name("MyView")
var topLevelObjects : NSArray?
Bundle.main.loadNibNamed(nibFile, owner:nil, topLevelObjects: &topLevelObjects)
let views = (topLevelObjects as! Array<Any>).filter { $0 is NSView }
let vc = ViewController()
vc.view = views[0] as! NSView
PlaygroundPage.current.liveView = vc
Run the playground and look in the Assistant Editor pane. You will see this:
EDIT It occurs to me that a more pleasant way to write this (placing the decision as to where to get its view in the hands of the view controller itself) would be as follows:
class ViewController : NSViewController {
override func loadView() {
let nibFile = NSNib.Name("MyView")
var topLevelObjects : NSArray?
Bundle.main.loadNibNamed(
nibFile, owner:nil, topLevelObjects: &topLevelObjects)
let views = (topLevelObjects as! Array<Any>).filter { $0 is NSView }
self.view = views[0] as! NSView
}
}
PlaygroundPage.current.liveView = ViewController()
Your error comes from because your ViewController() does not have any views. Here the link to the documentation.
If you pass in a nil for nibNameOrNil then nibName will return nil and
loadView will throw an exception; in this case you must invoke
setView: before view is invoked, or override loadView.
One possible fix:
let myVC = NSViewController()
myVC.view = NSView()
PlaygroundPage.current.liveView = myVC
In the second case have you set the file's owner of your NIB to your current view controller as the documentation said?
The specified nib file should typically have the class of the file's
owner set to NSViewController, or a custom subclass, with the view
outlet connected to a view.
You might want to include the following:
PlaygroundPage.current.liveView = ViewController()
PlaygroundPage.current.needsIndefiniteExecution = true
I am attaching the link to the similar question: How to setup ViewController in Playgrounds?
Let me know if it helps!

Swift 3+: Multiple Storyboards with TabBarController. Set TabBarController Tab, Segue to desired ViewController

Working in multiple storyboards. Getting from point A->TabBarController(tab 2)->NavigationControllerA->StoryboardReference->NavigationControllerB->ViewControllerA->ViewControllerB. No need for data passing.
My tab bar controller is set up in its own storyboard, with each tab going through a navigation controller to a storyboard reference. Then the storyboard reference links to another storyboard (obv.), and through a navigation controller to ViewControllerA. I then want to go to performSegue to ViewControllerB. This is all done from a UIViewController Extension.
From what I've been reading, I should approach like this:
let tab = 2
let sbOne = UIStoryboard(name: "Main", bundle: nil)
let tabBarController = sbOne.instantiateViewController(withIdentifier: "TabBarController") as! UITabBarController
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = tabBarController
tabBarController.selectedIndex = tab
let sbTwo = UIStoryboard(name: "sbTwo", bundle: nil)
let viewControllerA = sbTwo.instantiateViewController(withIdentifier: "ViewControllerA")
performSegue(withIdentifier: "toViewControllerB", sender: self)
I receive NSInvalidArgument: Point A "has no segue with identifier 'toViewControllerB'"
Swift 3+
I’d be surprised if this is the best answer but this is the only solution that worked. A bit of my problem stemmed from lack of understanding. But the concept I arrived at was to separate the functions into two parts. Setting the tabbarcontroller tab, then segueing to the destination using a variable struct.
I ended up setting a struct at Point A.
struct Destination {
static var currentID = Int()
}
And on button press I would set the variable with this and then and call the function below:
Destination.currentID = currentID
goToViewControllerA()
Then using part of the code I had before I could set the tabbar Controller as the root view controller and go to ViewControllerA (the first view controller) on the selected tab. This is done in an uiviewcontroller extension but can also be done in a function in Point A.
goToViewControllerA(){
let tab = 2
let sbOne = UIStoryboard(name: "Main", bundle: nil)
let tabBarController = sbOne.instantiateViewController(withIdentifier: "TabBarController") as! UITabBarController
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = tabBarController
tabBarController.selectedIndex = tab
}
It’s then just a matter of checking if the struct static var is set, and segueing to the destination controller (ViewControllerB). This is done in ViewControllerA, or if you have to segue through multiple view controllers you could model your function on each viewcontroller using this in the viewdidload.
If Destination.currentID != Int() {
performSegue(withIdentifier:“toViewControllerBSegue”, sender:self)
}
I’d love to learn a better way of doing this if it exists.

Change ViewController from Tabbar

I have a UITabBarController with 3 tabs and a SettingsVC where you can choose the appearance of MainVC (There are 2 different ViewControllers which based on what user prefers should be shown as the MainVC).
I made the application and it works but it's really buggy, cause I was just pushing the ViewControllers on top of each other, and in some certain conditions you see bugs while switching tabs.
Main.storyboard
Here is some part of my code(after trying a lot of stuff) which changes the VC's based on UserDefaults
CheckSettingsStatus.swift
//
// Check for Double Column Settings and navigate to Double Column VC
//
func checkMainVCViewStatusAndNavigateAcordingly(){
if UserDefaults.standard.integer(forKey: KEY_MAIN_VC_VIEW) == 1{
//let appDel = UIApplication.shared.delegate as! AppDelegate
//appDel.goToDoubleMainVC()
let mainVC = storyboard?.instantiateViewController(withIdentifier: "DoubleColumnMainVC") as! DoubleColumnMainVC
self.navigationController?.viewControllers[0] = mainVC
//navigationController?.pushViewController(mainVC, animated: false)
var navigationArray = self.navigationController?.viewControllers
print("Number of VCs = \(String(describing: navigationArray?.count))")
if navigationArray?.count == nil{
self.navigationController?.pushViewController(mainVC, animated: false)
}else{
self.navigationController?.popToRootViewController(animated: false)
}
// self.navigationController?.viewControllers = navigationArray!
}
}
//
// same as function above but goes to MainVC
//
func checkDoubleColumnMainVCViewStatusAndNavigateAcordingly(){
if UserDefaults.standard.integer(forKey: KEY_MAIN_VC_VIEW) == 0{
// let appDel = UIApplication.shared.delegate as! AppDelegate
// appDel.goToMainVC()
let mainVC = storyboard?.instantiateViewController(withIdentifier: "MainVC") as! MainVC
self.navigationController?.viewControllers[0] = mainVC
var navigationArray = self.navigationController?.viewControllers
print("Number of VCs = \(String(describing: navigationArray?.count))")
if navigationArray?.count == nil{
navigationController?.pushViewController(mainVC, animated: false)
}else{
self.navigationController?.popToRootViewController(animated: false)
}
// self.navigationController?.viewControllers = navigationArray!
}
}
With this in ViewWillAppear of both this Controllers I call these functions to switch accordingly.
Any ideas what I'm doing wrong? Can I push controllers from UITabBarController and not NavigationController? something like:
tabBarController.pushViewController(mainVC, animated: false)
instead of:
navigationController.pushViewController(mainVC, animated: false)
Thank you in advance
UPDATE:
In my best case when everything works nicely, on first launch of the app (to the second mainVC) the Buttons in NavBar doesn't work.
EDIT:
I just realised that the NavigationBar that I'm seeing above my SecondMainVC is the NavigationBar from MainVC, that is why the buttons are not working. how is that possible?
I faced same issue while trying to develop a tab bar controller in storyboard , for this reason I found that the best way is to implement it by code.
so you can make your code look like this :
1) create a a sub class of UITabController and let it conform to UITabBarControllerDelegate
class TabBarVC: UITabBarController , UITabBarControllerDelegate {
2)then write this method in your class to initialize the tabs in your project:
private func settingUpTabBarAndInitializingViewControllers(){
//self delagting
self.delegate = self
//home tab
let homeStoryboard = UIStoryboard(name: Constants.StoryBoards.homeStoryboard, bundle: nil)
let homeVC = homeStoryboard.instantiateViewController(withIdentifier: Constants.ControllersIDs.homeScreen) as! HomeVC
// shop tab
let shopStoybaord = UIStoryboard(name: Constants.StoryBoards.shopStoryboard, bundle: nil)
let shopVC = shopStoybaord.instantiateViewController(withIdentifier: Constants.ControllersIDs.shopScreen) as! ShopVC
//offers tab
let offersStoryboard = UIStoryboard(name: Constants.StoryBoards.offersStoryboard, bundle: nil)
let offersVC = offersStoryboard.instantiateViewController(withIdentifier: Constants.ControllersIDs.offersScreen) as! OffersVC
//more tab
let moreStoryboard = UIStoryboard(name: Constants.StoryBoards.MoreScreenStoryboard, bundle: nil)
let moreVC = moreStoryboard.instantiateViewController(withIdentifier: Constants.ControllersIDs.moreVCScreen) as! MoreOptionsTVC
//setting VCs
self.viewControllers = [homeVC , shopVC , offersVC , moreVC]
//setting buttons
//buttons images
let homeImage = UIImage(named: "TabHome")
let shopImage = UIImage(named : "TabShop")
let offerImage = UIImage(named: "TabOffers")
let moreImage = UIImage(named: "TabMenu")
// buttons shakhsiyan
let homeButton = UITabBarItem(title: homeText, image: homeImage, selectedImage: homeTappedImage)
let shopButton = UITabBarItem(title: shopText, image: shopImage, selectedImage: shopTappedImage)
let offersButton = UITabBarItem(title: offersText, image: offerImage, selectedImage: offersTappedImage)
let moreButton = UITabBarItem(title: moreText, image: moreImage, selectedImage: moreTappedImage)
homeVC.tabBarItem = homeButton
shopVC.tabBarItem = shopButton
offersVC.tabBarItem = offersButton
moreVC.tabBarItem = moreButton
}
P.S. you can add tabs as much as you want and while scrolling in them you wont face any bug,
make sure to have only one navigation controller before your tab bar controller.
Check this hope it helps
Ok you can create a DEMO to see how thing works.After that you can implement it in your project.I know it may not look good to you but read it once and see image attached i am sure will get your answer.
1.I have created two UIViewControllers with the UITabBarController.
2.I have created class ItemFirstVC,ItemTwoVC and assigned to UIViewControllers in storyboard
3.Each controller is embedded in UINavigationController
4.Create a custom class of UITabBarController
5.Assign it to UITabBarController in storyboard
6.Now Each UIViewController has a button named as Firs Button,Second Button
7.From each button i have connected Action Segue to one more controller as you can see in image.
8.Now i have created a third UIViewController which is separate from UITabBarController.
9.Now we will load that Third UIViewController in UITabBarController
10.Create one more class ItemThirdVC and assign it to third UIViewController.
11.It also has a button named as third button action segued to one more UIViewController
12.Now switch to custom class of UITabBarContoller
// variable required to see work in action
var count = 0
class CustomTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// remember to assign delegate
delegate = self
}
// this delegate indicates whether this tab should be selected or not
extension CustomTabBarController : UITabBarControllerDelegate{
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
// switch to UIViewController tabs multiple times
count = count + 1
if(count == 5){
// when count reaches 5 a new controller will load
let navigationController = viewController as? UINavigationController
guard navigationController != nil else {return true}
let viewController = storyboard?.instantiateViewController(withIdentifier: "ItemThreeVC")
guard let vc = viewController else {return true}
navigationController?.setViewControllers([vc], animated: false)
}
return true
}
}

Swift 2 - Open Split View Controller

I have a UISplitViewController in my iOS app, but my initial view controller is a normal UIViewController.
I want to open the UISplitViewController when a button is clicked on the UIViewController:
#IBAction func openSplitViewController(sender: AnyObject) {
let splitViewController = UISplitViewController()
let leftNavController = splitViewController.viewControllers.first as! UINavigationController
let masterViewController = leftNavController.topViewController as! MenuTableViewController
let rightNavController = splitViewController.viewControllers.last as! UINavigationController
let detailViewController = rightNavController.topViewController as! DetailViewController
splitViewController.viewControllers = [masterViewController,detailViewController];
self.presentViewController(splitViewController, animated: true, completion: nil)
}
But when I click the button, I get:
fatal error: unexpectedly found nil while unwrapping an Optional value
How should I be opening the UISplitViewController from the UIViewController?
Your error has nothing to do with a UISplitViewController, the error you get is because one or more of your arguments is nil.
So make sure that all of the viewControllers you create are real and not nil.
Look for instance at this line:
let leftNavController = splitViewController.viewControllers.first as! UINavigationController
At this point your splitViewController has no viewControllers assigned, so leftNavController will be nil. You will need to create the navController and viewController before adding them to your splitViewController.