How would I reference the main storyboard in my app programmatically in swift? - xcode

How would I reference the main storyboard in my app programmatically in swift? I looked into the app delegate for a reference but so far I haven't found one.

Oh whoops I found the answer...
In another view controller of the that is connected to a storyboard you can simply use:
self.storyboard?

Or any object can get a storyboard by referencing its name and bundle:
let storyboard = UIStoryboard(name: "storyboardNameHere", bundle: nil) //if bundle is nil the main bundle will be used

It's easy. When I've come across a similar problem, I wrote a class that can obtain any resources from main bundle.
//Generate name of the main storyboard file, by default: "Main"
var kMainStoryboardName: String {
let info = NSBundle.mainBundle().infoDictionary!
if let value = info["TPMainStoryboardName"] as? String
{
return value
}else{
return "Main"
}
}
public class TPBundleResources
{
class func nib(name: String) -> UINib?
{
let nib = UINib(nibName: name, bundle: NSBundle.mainBundle());
return nib
}
//Main storybord
class func mainStoryboard() -> UIStoryboard
{
return storyboard(kMainStoryboardName)
}
class func storyboard(name: String) -> UIStoryboard
{
let storyboard = UIStoryboard(name: name, bundle: NSBundle.mainBundle())
return storyboard
}
//Obtain file from main bundle by name and fileType
class func fileFromBundle(fileName: String?, fileType: String?) -> NSURL?
{
var url: NSURL?
if let path = NSBundle.mainBundle().pathForResource(fileName, ofType: fileType)
{
url = NSURL.fileURLWithPath(path)
}
return url
}
class func plistValue(key:String) -> AnyObject?
{
let info = NSBundle.mainBundle().infoDictionary!
if let value: AnyObject = info[key]
{
return value
}else{
return nil
}
}
}
public extension TPBundleResources
{
//Obtain view controller by name from main storyboard
class func vcWithName(name: String) -> UIViewController?
{
let storyboard = mainStoryboard()
let viewController: AnyObject! = storyboard.instantiateViewControllerWithIdentifier(name)
return viewController as? UIViewController
}
class func vcWithName(storyboardName:String, name: String) -> UIViewController?
{
let sb = storyboard(storyboardName)
let viewController: AnyObject! = sb.instantiateViewControllerWithIdentifier(name)
return viewController as? UIViewController
}
//Obtain view controller by idx from nib
class func viewFromNib(nibName: String, atIdx idx:Int) -> UIView?
{
let view = NSBundle.mainBundle().loadNibNamed(nibName, owner: nil, options: nil)[idx] as! UIView
return view
}
class func viewFromNib(nibName: String, owner: AnyObject, atIdx idx:Int) -> UIView?
{
let bundle = NSBundle(forClass: owner.dynamicType)
let nib = UINib(nibName: nibName, bundle: bundle)
let view = nib.instantiateWithOwner(owner, options: nil)[idx] as? UIView
return view
}
class func viewFromNibV2(nibName: String, owner: AnyObject, atIdx idx:Int) -> UIView?
{
let view = NSBundle.mainBundle().loadNibNamed(nibName, owner: owner, options: nil)[idx] as! UIView
return view
}
}
Here are simple examples:
//Get a main storyboard
TPBundleResources.mainStoryboard()
//Get view controller form main storyboard
TPBundleResources.vcWithName("MyViewController")
//Get view from MyView.nib at index 0
TPBundleResources.viewFromNib("MyView", atIdx: 0)
//Get plist value by key
TPBundleResources.plistValue("key")

Even if #ManOfPanda's answer is correct, there are cases where you simply don't have a reference to a UIViewController, so you can grab it from the rootViewController of the UIWindow object from your AppDelegate.
// First import your AppDelegate
import AppDelegate
// ...
// Then get a reference of it.
let appDelegate = UIApplication().shared.delegate as! AppDelegate
// From there, get your UIStoryboard reference from the
// rootViewController in your UIWindow
let rootViewController = appDelegate.window?.rootViewController
let storyboard = rootViewController?.storyboard
You could also, of course, simply create a UIStoryboard by using (as #Mario suggested):
let storyboard = UIStoryboard(name: "storyboard", bundle:nil)
But that will, according to Apple documentation, create a new instance of the Storyboard (even if you already have one working). I always prefer to use an existing instance.
init(name:bundle:)
Creates and returns a storyboard object for the specified storyboard resource file.
init(name: String, bundle storyboardBundleOrNil: Bundle?)
Parameters
name: The name of the storyboard resource file without the filename extension. This method raises an exception if this parameter is nil.
storyboardBundleOrNil: The bundle containing the storyboard file and its related resources. If you specify nil, this method looks in the main bundle of the current application.
Source: Apple documentation

UIStoryboard * mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];

Related

SwiftUI: How do I access a controller from within a coordinator (MKMapView delegate) inside a UIViewRepresentable?

Edit:
MapViews have the ability to show locations by adding pins (or custom views) to the map. I am attempting the simplest version of customization, where I am just trying to replace the default popup view (Title, Subtitle, Info symbol (i with a circle around it)) with a custom view.
Using a MKMapView from SwiftUI requires using a UIViewRepresentable class with a coordinator to handle its delegate, because MKMapView is a UIKit class, not a SwiftUI struct.
In a UIViewController, I could add a view (or a tableviewcontroller's main view even) as a subview to the annotationView. My question is how to do that using a native SwiftUI struct View.
I am trying to add a custom detailCalloutAccessoryView to a MKPinAnnotation (MKAnnotationView)
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKPinAnnotationView
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
addDropDown(annotationView: annotationView)
}
Here's are two attempts at writing addDropDown():
func addDropDown(annotationView: UIView?) {
guard let annotationView = annotationView as? MKPinAnnotationView else { return }
// This is a UIKit UITableViewController Instance
let tvc = RandomCapitalDropDown(nibName: nil, bundle: nil)
annotationView.addSubview(tvc.view)
// error: Cannot convert value of type 'MKPinAnnotationView' to expected argument type 'UIViewController?'
tvc.didMove(toParent: annotationView)
tvc.view.leadingAnchor.constraint(equalTo: annotationView.leadingAnchor).isActive = true
tvc.view.trailingAnchor.constraint(equalTo: annotationView.trailingAnchor).isActive = true
tvc.view.topAnchor.constraint(equalTo: annotationView.topAnchor).isActive = true
tvc.view.bottomAnchor.constraint(equalTo: annotationView.bottomAnchor).isActive = true
}
// This is for a SwiftUI Struct called `PinDropDown`
func addDropDown(annotationView: UIView?) {
guard let annotationView = annotationView as? MKPinAnnotationView else { return }
let child = UIHostingController(rootView: PinDropDown(gameMode: .byContinent(continent: 0)))
child.view.translatesAutoresizingMaskIntoConstraints = false
child.view.frame = parent.view.bounds
// parent is UIViewRepresentable, so there is no view - this won't work
parent.view.addSubview(child.view)
// same issue
parent.addChild(child)
}
I have reviewed:
SwiftUI UIViewRepresentable and Custom Delegate
and
Access controller method from inside a model
and
How to find the frame of a swiftui uiviewrepresentable
none of these answered my question.

Cocoa - Present NSViewController programmatically

Generally, We can able to display next view controller from first view controller by having different kind of NSStoryboardSeque like Present, Show, Sheet etc., But, How we can achieve the same programmatically?.
Comparing with UIViewController, presenting a view controller modally by presentViewController:animated:. Is there any same kind of approach for NSViewController?
Thanks in advance.
The two different presentation types I use are:
func presentViewControllerAsModalWindow(_ viewController: NSViewController)
func presentViewControllerAsSheet(_ viewController: NSViewController)
After doing some more research another way to do using:
func presentViewController(_ viewController: NSViewController, animator: NSViewControllerPresentationAnimator)
And eating a custom presentation animator. Here you have the freedom to do what you like :)
In case someone is looking for the solution in 2022,
extension NSViewController {
func presentInNewWindow(viewController: NSViewController) {
let window = NSWindow(contentViewController: viewController)
var rect = window.contentRect(forFrameRect: window.frame)
// Set your frame width here
rect.size = .init(width: 1000, height: 600)
let frame = window.frameRect(forContentRect: rect)
window.setFrame(frame, display: true, animate: true)
window.makeKeyAndOrderFront(self)
let windowVC = NSWindowController(window: window)
windowVC.showWindow(self)
}
}
1.Create a NSViewController instance with StoryBoard Identifier
let theTESTVCor = self.storyboard?.instantiateController(withIdentifier: "TESTVCor") as! NSViewController
2.Present In Via the current NSViewController
theNSViewController.presentViewControllerAsModalWindow(theTESTVCor)
⚠️ DO NOT FORGET to set the Identifier of the NSViewController in Storyboard
If you have a view controller (presenting) than it's as simple as following function are provided:
open func presentAsSheet(_ viewController: NSViewController)
open func presentAsSheet(_ viewController: NSViewController)
open func present(_ viewController: NSViewController, asPopoverRelativeTo positioningRect: NSRect, of positioningView: NSView, preferredEdge: NSRectEdge, behavior: NSPopover.Behavior)
If you need to present a view controller in a new window (NOT MODAL) you need to create own NSWindow, NSWindowController
let gridView = NSGridView(views: [
[NSTextField(labelWithString: "label1"),NSTextField(labelWithString: "label2")],
[NSTextField(labelWithString: "label3"),NSTextField(labelWithString: "label4")]
])
let viewController = NSViewController()
viewController.view = gridView
let window = NSWindow(contentViewController: viewController)
window.center()
let windowController = NSWindowController(window: window)
windowController.showWindow(nil)
EXPLANATION:
Storyboards are using seques to perform some magic. The show seque is simply calling action "perform:" on object NSStoryboardShowSegueTemplate ([NSApp sendAction:to:from]). This seque will create NSWindowController and NSWindow (private method windowWithContentViewController:) for you and on top it will layoutSubviews/resize and center the window. Magic bonus is self retaining the window so you don't care about memory management.
Example of programatic calling (using Storyboards to instantiate windowController with viewController)
import Cocoa
import Contacts
class ShorteningHistoryWindowController : NSWindowController, Storyboarded {
static var defaultStoryboardName = "ShorteningHistory"
}
struct ShorteningHistory {
static let shared = ShorteningHistory()
private var windowController : NSWindowController
private init() {
windowController = ShorteningHistoryWindowController.instantiate()
}
public func showHistory() {
windowController.showWindow(self)
}
}
extension Storyboarded where Self: NSWindowController {
static var defaultStoryboardName: NSStoryboard.Name { return String(describing: self) }
static var defaultIdentifer: NSStoryboard.SceneIdentifier {
let fullName = NSStringFromClass(self)
let className = fullName.components(separatedBy: ".")[1]
return className
}
static func instantiate() -> Self {
let storyboard = NSStoryboard(name: defaultStoryboardName, bundle: Bundle.main)
guard let vc = storyboard.instantiateController(withIdentifier: defaultIdentifer) as? Self else {
fatalError("Could not instantiate initial storyboard with name: \(defaultIdentifer)")
}
return vc
}
}
PS: Don't forget to set Storyboard Identifiers in Storyboard

Could not cast value of type 'UINavigationController' (0x10836e698) to 'UITabBarController' (0x10836e6e8).?

This is the code for UITabBarController in which I am trying to open a splitviewcontroller.
import UIKit
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
///here after this line I am getting error
var mainCont : UITabBarController = ((UIApplication.sharedApplication().delegate) as! AppDelegate).window?.rootViewController as! UITabBarController
var navCont2 : UINavigationController? = mainCont.viewControllers?[1] as? UINavigationController
var controller = UIStoryboard(name: "Storyboard2", bundle: nil).instantiateInitialViewController() as! UISplitViewController
controller.preferredDisplayMode = UISplitViewControllerDisplayMode.AllVisible
navCont2?.presentViewController(controller, animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Your first line:
var mainCont : UITabBarController = ((UIApplication.sharedApplication().delegate)
as! AppDelegate).window?.rootViewController as! UITabBarController
Is getting the window's rootViewController as UITabBarController. And the error message is really kind of clear:
Could not cast value of type 'UINavigationController' (0x10836e698) to 'UITabBarController' (0x10836e6e8).
On app start the window's rootViewController is set to what ever you have defined as your initial view controller on the Storyboard (The big grey arrow). My guess is that your initial view controller is set to a UINavigationController, but in your code you are trying to cast (force) it to be a UITabBarController.

Could not cast value of type 'UINavigationController'

I am implementing a search interface for my application, so basically I will pass the search keyword from one ViewController to another ViewController.
I have done this type of parameter passing several times but something seems strange this time. The destination ViewController is embedded in a Navigation Controller.
Now the code looks something like this.
override func prepareForSegue(segue: UIStoryboardSegue?, sender: AnyObject!) {
if (segue?.identifier == "home_to_search") {
var searchKeyword = txtSearchBarHome.text
var svc = segue?.destinationViewController as! SearchViewController;
svc.toPassSearchKeyword = searchKeyword;
//let nav = segue?.destinationViewController as! UINavigationController
//let svc = nav.topViewController as! SearchViewController
//svc.toPassSearchKeyword = searchKeyword;
}
}
I have already explored these questions
DestinationViewController Segue and UINavigationController swift, How do I segue values when my ViewController is embedded in an UINavigationController? with no luck.
What makes me wonder is that I already have ViewControllers embedded in Navigation Controllers that I can pass parameters by segueing. However, in this case it throws a Could not cast value of type 'UINavigationController' error. If I try the commented code above it does not throw an error there, but at the AppDelegate class.
Answering my own question. In this case we need to access the child view by doing something like this:
let nav = segue?.destinationViewController as! UINavigationController
let svc = nav.topViewController as! SearchViewController
svc.toPassSearchKeyword = searchKeyword;
Based on Semih's answer, you can also do this to pass a variable to next segue if you don't want to use an identifier:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let nav = segue.destination as? UINavigationController,
let vc = nav.topViewController as? TestViewController {
vc.username = "Test"
}
}
Just a quick note for those looking into this issue it has been renamed to Destination
let nav = segue.destination as! UINavigationController
let svc = nav.topViewController as! SearchViewController
svc.toPassSearchKeyword = searchKeyword;

How to open a new window with its own ViewController from AppDelegate in Swift

I have made a statusBar application with a drop down. I would like to open a settingsWindow from that dropdown. I have made the settings window with its own ViewController.
The issue is that i can't figure out how to instantiate and show the settingsWindow that i have made. I have tried to follow every thread on the internet without any success.
My Viewcontroller:
class SettingsViewController: NSViewController {
#IBOutlet var ipAddress: NSTextField!
#IBOutlet var port: NSTextField!
#IBAction func connect(sender: AnyObject) {}
override func viewDidLoad() {
super.viewDidLoad()
}
}
My AppDelegate:
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet var statusMenu: NSMenu!
var statusItem: NSStatusItem?
var tcpService: TcpService = TcpService()
func applicationDidFinishLaunching(aNotification: NSNotification?) {
let bar = NSStatusBar.systemStatusBar()
statusItem = bar.statusItemWithLength(20)
statusItem!.menu = statusMenu
statusItem!.image = NSImage(byReferencingFile: NSBundle.mainBundle().pathForResource("16*16", ofType: "png"))
statusItem!.highlightMode = true
tcpService.initOutputStream("192.168.1.1", Port: 8888)
}
func applicationWillTerminate(aNotification: NSNotification?) {
// Insert code here to tear down your application
}
#IBAction func openSettings(sender: AnyObject) {
// open settings for ip and port optional port
}
}
in swift 3:
var myWindow: NSWindow? = nil
let storyboard = NSStoryboard(name: "Main",bundle: nil)
let controller: EditorViewController = storyboard.instantiateController(withIdentifier: "editorViewController") as! ViewController
myWindow = NSWindow(contentViewController: controller)
myWindow?.makeKeyAndOrderFront(self)
let vc = NSWindowController(window: myWindow)
vc.showWindow(self)
For 2022
in your normal Main storyboard, tap to add a new window controller.
tap precisely on the red "X", then the blue circle, and then enter "ExampleID" at the green entry.
in your app's ordinary main view controller, add this
variable:
var otherWindow: NSWindowController?
function:
private func otherWindow() {
let sb = NSStoryboard(name: "Main", bundle: nil)
otherWindow = sb.instantiateController(
withIdentifier: "ExampleID") as! NSWindowController
otherWindow?.showWindow(self)
}
That's it.
Call otherWindow when you want to.
Problem:
Inevitably you will want to set up the otherWindow in a certain way, example, transparent, whatever. Unfortunately this is a whole topic in itself, but you do it like this:
private func otherWindow() {
... as above ...
otherWindow?.window?.ExampleSetup()
}
and then
extension NSWindow {
func ExampleSetup() {
self.styleMask = .borderless
self.collectionBehavior = [.fullScreenPrimary]
self.level = .floating
self.isMovable = false
self.titleVisibility = .hidden
// etc etc etc ..
guard let screen = self.screen ?? NSScreen.main else {
print("what the???")
return
}
self.setFrame(screen.frame, display: true)
// consider also .visibleFrame
}
}
enum Storyboards: String {
case main = "Main"
func instantiateVC<T>(_ identifier: T.Type) -> T? {
let storyboard = NSStoryboard(name: rawValue, bundle: nil)
guard let viewcontroller = storyboard.instantiateController(withIdentifier: String(describing: identifier)) as? T else { return nil}
return viewcontroller
}
}
var ssoLoginController: IDSSOLoginViewController?
var myWindow: NSWindow? = nil
ssoLoginController = Storyboards.main.instantiateVC(IDSSOLoginViewController.self)
myWindow = NSWindow(contentViewController: ssoLoginController!)
myWindow?.makeKeyAndOrderFront(self)
let vc = NSWindowController(window: myWindow)
vc.showWindow(self)
I am not 100% that I fully understand your problem, but assuming that you are using a storyboard (you should if you are starting fresh), adding few lines to your applicationDidFinishLaunching method will help:
var myWindow: NSWindow? = nil
let storyboard = NSStoryboard(name: "Main",bundle: nil)
let controller: SettingsViewController = storyboard?.instantiateControllerWithIdentifier("SettingsViewController") as SettingsViewController
myWindow = controller.window
myWindow?.makeKeyAndOrderFront(self)
Do not forget to set the Storyboard ID in IB (in the example above to SettingsViewController)!

Resources