Menubar app with NSWindowController - swift

I'm currently trying to build a menubar app. Therefore I need a NSWindowController for an login field. It must be possible to open this NSWindowController when pressing a menu item and also close that window when the user clicked on cancel.
I used showWindow(self) and NSApp.hide(self) but this didn’t work for me. So has anyone an idea what I can try?

Assuming you are using Storyboard
Add an NSWindowController to the storyboard and uncheck visible at launch of the window.
In AppDelegate create a property windowController
var windowController : NSWindowController!
In AppDelegate create an IBAction.
In the action get the main storyboard with
let mainStoryBoard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil)
Then instantiate and assign the window controller (the identifier must match the storyboard identifier)
windowController = mainStoryBoard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "Login")) as! NSWindowController
Get the associated view controller (LoginController is the custom class of the view controller)
let loginController = windowController.window!.contentViewController as! LoginController
Show the main window
windowController.showWindow(self)
In Interface Builder connect the NSMenuItem to First Responder (red cube) and then to the created IBAction.
You can close the window either with the red close button or you need to add custom logic.
If you use a XIB create an NSWindowController subclass and load the XIB with windowController = MyWindowController(window: nil), activate your app with NSApp.activate(ignoringOtherApps: true) get the associated window with let controllerWindow = windowController.window! and show the window with controllerWindow.makeKeyAndOrderFront(self)

Related

Swift -injecting vars in ViewControllers

I am proudly new to iOS developing and I am trying to build my first app. I am doing a course on an online platform which does the following in the
AppDelegate -> application didFinishLaunchingWithOptions:
let navigationController = window?.rootViewController as! UINavigationController
let notebooksListViewController = navigationController.topViewController as! NotebooksListViewController
notebooksListViewController.dataController = dataController
This app has a Navigation controller which begins with an UIViewController.
I have 2 questions here, first is why this works, I mean, I am in AppDelegate, so the NotebooksListViewController (first view of the app) is not instantiated yet (I think), so why I am able to inject a variable in it?
On the other hand, the second question, is how can I do this in a different scene? I have a TabBarViewController as first scene, and the first tab is a UITableViewController and I want to inject the same way my dataController var, how can I accomplish this? I could not get to do it, neither understand it.
Thanks in advance.
It works, because of some Xcode magic:
In your Target Setting, General tab, the Main Interface entry specifies the name of the Storyboard to be loaded automatically when your app starts up:
In the storyboard, the Initial View Controller then will be instantiated. It seems like this is an UINavigationController.
Since this is done automatically, it just works - until you want to do something special :-)
If you want to start up with a different scene - maybe from a different view controller - you could just change either the Main Interface to another storyboard, the Initial View Controller (inside the storyboard) or both.
Or, you could just start up by yourself, by leaving the Main Interface empty and create your own view controller inside the app delegate (didFinishLaunchingWithOptions), something like
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if let tabVC = mainStoryboard.instantiateViewControllerWithIdentifier("TabCtrl") as? UITabBarController {
self.window?.rootViewController = tabVC
// Access the subcontrollers, or create them
// Initialize their values
// tabVC.viewControllers[0].data = ...
} else {
// Ooops
}
self.window?.makeKeyAndVisible()
Answer to your first question
as the method name is self explanatory didFinishLaunchingWithOptions means your application is didfinish with launching with options and its about to enter in foreground so here application need to set rootViewController so in this method controller you want to set as view controller is initiated thats why you can inject variable in it
answer to second question
let navigationController = window?.rootViewController as! UITabbarController
let VC = navigationController.childViewController
//Now Using VC you can access all you controller of tabbar controller
let notebooksListViewController = navigationController.topViewController as!
NotebooksListViewController
notebooksListViewController.dataController = dataController
now as shown above you can use VC to access you view controllers
but be careful here because VC return viewcontroller array so you need make checks for perticular VC you want to access

Launching ViewController as sheet from windowDidLoad

I'm trying to show a storyboard as a sheet right after the window has loaded.
override func windowDidLoad() {
super.windowDidLoad()
let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Init"), bundle: nil)
let controller = storyboard.instantiateInitialController() as! NSViewController
self.window!.contentViewController?.presentViewControllerAsSheet(controller)
}
Unfortunately, the sheet is shown out of position and behind the window.
When I run the same code inside a button, everything works fine.
Screenshot
How do i correctly show a storyboard sheet after loading a window?
I have more knowledge of the iOS ecosystem but I suppose that you should show new windows only after the origin window has been shown. windowDidLoad is called when the view has been loaded, not when it has been shown.
Therefore, you should probably put your code into viewDidAppear of the contentViewController.

Opening window + view from an other view controller

I've got a ViewControllerOne. ViewControllerOne is connected via Ctrl-Drag (in storyboard) to a menu-button mBtn (which means I don't know how it is implemented programmatically).
Clicking on this mBtn, a ViewOne appears (present modal). This ViewOne is bound to ViewControllerOne. ViewOne has a button btnOne.
Clicking on btnOne I want ViewOne to be dismissed and ViewTwo to be shown. ViewTwo belongs to ViewControllerTwo and to WindowControllerTwo.
The WindowControllerTwo-ViewControllerTwo-binding is the standard case as created on a new project.
I have the following code in the IBAction for button btnOne in ViewControllerOne:
#IBAction func onbtnOnePressed(sender: AnyObject){
let m_WindowControllerTwo = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil).instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("WindowControllerTwo")) as! NSWindowController // I have no custom class for the window controller as I don't really know what I can use it for ...
let m_ViewTwo = WindowControllerTwo.contentViewController as! ViewControllerTwo // my custom class for ViewTwo
m_ViewTwo.attributeIWantToPassToThisView = self.x // an attribute I want to pass from view a to view b
m_WindowControllerTwo.contentViewController = m_ViewTwo // passing the attribute from a to b
m_WindowControllerTwo.showWindow(self) // this does not work
self.dismiss(nil) // see NOTE
}
This code actually does not work. On debugging it step by step, I'm seeing the window/view flickering but not appearing...
NOTE: I could connect the button btnOne with a ctrl-drag to ViewControllerTwo. This works. But then the current ViewOne does not get dismissed!
Question: What am I doing wrong here? In iOS swift this also works. I don't quite get the WindowController stuff, so I'll need your advice on this.
Instead of this: m_WindowControllerTwo.showWindow(self)
use:
let application = NSApplication.shared()
application.runModal(for: wordCountWindow) //this will present WindowControllerTwo modally.
then to close your present controller add this line: PresentWindowControllerName.close()

Using NSpopover in swift. How to change top bar

I am using this code to show the VC when someone clicks on the preference pane.
let popover = NSPopover()
popover.contentViewController = NSStoryboard(name: "Main", bundle: nil).instantiateControllerWithIdentifier("AuthVC") as? NSViewController
popover.animates = true
popover.behavior = NSPopoverBehavior.Transient
When the view shows up, the top bar is a standard bar. I want it to be like this with an arrow:
When I use this code: popover.showRelativeToRect(CGRectNull, ofView: ViewController , preferredEdge: NSRectEdge) In ofView It wants an NSView However mine is a ViewController.
You have to use -showRelativeToRect:ofView:preferredEdge: and pass an empty rect and the view in the menu to get the anchor (arrow).

Why is drawRect not called in the NSView when I programmatically load my window from a storyboard?

I have a window in my Main.storyboard file that I am trying to load programmatically, and not at application launch. So I have deleted the "Storyboard Entry Point" entirely, and in AppDelegate.swift, I have:
let storyBoard = NSStoryboard(name: "Main", bundle: nil)
let pauseWindowController = storyBoard.instantiateControllerWithIdentifier("pauseWindowController")
pauseWindowController.showWindow(self)
This seems to work, but when I run the app, I get no window. I added tracing code in my app to follow the progression of events, and I find that my custom NSView subclass is initialized, but drawRect is never called. If I remove the above code above and re-add the Storyboard Entry Point (to point to the window controller), everything works fine.
Here is the result of my print calls that I added for tracing, which show the calling class and the method called:
(AppDelegate) applicationDidFinishLaunching
(Pause OverlayWindow : NSWindow) Window init
(PauseOverlayView : NSView) View init
(PauseViewController: NSViewController) viewDidLoad
(PauseViewController: NSViewController) viewDidAppear
(PauseViewController: NSViewController) viewWillDisappear
(PauseViewController: NSViewController) viewDidDisappear
There should be a line (PauseOverlayView : NSView) drawRect line after viewDidAppear, and the window should display, but the method is never called. Why?
I have "Visible At Launch" checked in the Storyboard editor for my window. To my understanding, drawRect is called if the view is dirty and visible. I imagine it's not visible for some reason, but can't find out why.
I found my mistake: I didn't add the pauseWindowController property (var pauseWindowController : NSWindowController!) to the top of my AppDelegate.swift file. And the code should then become
let storyBoard = NSStoryboard(name: "Main", bundle: nil)
pauseWindowController = storyBoard.instantiateControllerWithIdentifier("pauseWindowController") as! NSWindowController
pauseWindowController.showWindow(self)