OS X storyboard: how to show a window programmatically? - macos

I am creating an OS X status bar application.
I am trying to achieve the following:
app starts invisible, with menu bar item
click on menu bar item shows the main window
on deactivate, the window is hidden
So I am trying to programmatically show the main window when the menu item is clicked, but with no success.
My main window has "Hide on deactivate" checked. Once hidden, I cannot make it visible again using code.
Here is the code I have for now, but it doesn't work:
#IBAction func menuClick(sender: AnyObject) {
var mainWindow = NSStoryboard(name: "Main", bundle: nil)?.instantiateInitialController()
mainWindow?.makeKeyAndOrderFront(self)
}

This is how you have to do to show your Windows programmatically:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
let mainWindow = NSWindow(contentRect: NSMakeRect(0, 0, NSScreen.mainScreen()!.frame.width/2, NSScreen.mainScreen()!.frame.height/2), styleMask: NSTitledWindowMask|NSResizableWindowMask|NSMiniaturizableWindowMask|NSClosableWindowMask, backing: NSBackingStoreType.Buffered, defer: false)
func createNewWindow(){
mainWindow.title = "Main Window"
mainWindow.opaque = false
mainWindow.center()
mainWindow.hidesOnDeactivate = true
mainWindow.movableByWindowBackground = true
mainWindow.backgroundColor = NSColor(calibratedHue: 0, saturation: 0, brightness: 1, alpha: 1)
mainWindow.makeKeyAndOrderFront(nil)
}
func applicationDidFinishLaunching(aNotification: NSNotification) {
// lets get rid of the main window just closing it as soon as the app launches
NSApplication.sharedApplication().windows.first!.close()
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
#IBAction func menuClick(sender: AnyObject) {
createNewWindow()
}
}
or you can create an optional NSWindow var to store your window before you close it as follow
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var defaultWindow:NSWindow?
func applicationDidFinishLaunching(aNotification: NSNotification) {
// lets get rid of the main window just closing it as soon as the app launches
defaultWindow = NSApplication.sharedApplication().windows.first as? NSWindow
if let defaultWindow = defaultWindow {
defaultWindow.close()
}
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
#IBAction func menuClick(sender: AnyObject) {
if let defaultWindow = defaultWindow {
defaultWindow.makeKeyAndOrderFront(nil)
}
}
}

The makeKeyAndOrderFront method is a NSWindow method, but instantiateInitialController returns the window controller, not its window.
Also, if the window is hidden on deactivate, you wouldn't want to instantiate another copy. Keep a reference to the window and re-show that.
Finally, you may need to bring the app to the front too. Call [NSApp activateIgnoringOtherApps:YES] (or the Swift equivalent).

Related

Replace NSViewController under Swift2 Storyboard MAC OSX

I am new to Mac OSX and with Apple promoting the fact that the bodies of code are becoming similar decided to tell the folk I am writing code for we should be able to do a Mac OSX version. iPhone and iPad versions are all good and about to release second version so no issues there.
So I am subclassing NSWindowController to get access to the Toolbar and worked out how to remove and add items on the toolbar, but for the life of me I can not get one NSViewController (firstViewController) to dismiss and bring up the second NSViewController (secondViewController) in the same NSWindowController.
So the 2 issues are that
1. I want to be able to performSegueWithIdentifier from the first NSViewController in code and
2. bring up the second NSViewController by replacing the first NSViewController in the same NSWindowController.
If I add a button to the firstViewController and put a segue to the secondViewController then when I select the button the secondViewController comes up just fine but in a seperate window not the same NSWindowController that I want it to and the firstViewController does not get replaced but stays in the NSWindowController.
So I know the segue idea will work but its not working in code and when I do insert the segue from a button it works but into a seperate NSViewController that is not part of the NSWindowController.
I am trying to find some programming guide from Apple on the issue but no luck so far.
Here is an overview from my Storyboard:
Here is my NSWindowController subclassed and the func loginToMe2Team is trigger from the NSToolBar and its working just find as the print statements show up on the console.
import Cocoa
class me2teamWindowsController: NSWindowController {
#IBOutlet var mySignUp : NSToolbarItem!
#IBOutlet var myToolbar : NSToolbar!
let controller = ViewController()
override func windowDidLoad() {
super.windowDidLoad()
print("window loaded")
}
override func windowWillLoad() {
print("window will load")
}
#IBAction func logInToMe2Team(sender: AnyObject){
controller.LogIn() //THIS IS THE FUNC I AM TESTING WITH
}
#IBAction func signUpToMe2Team(sender: AnyObject){
controller.signUp()
}
Here is my NSViewController subclassed with the func LogIn. Its getting selected just fine but the performSegueWithIdentifier is not. And I did cut and past the Identifier to make absolutely sure it was the same.
import Cocoa
import WebKit
class ViewController: NSViewController {
#IBOutlet weak var theWebPage: WebView!
#IBOutlet weak var progressIndicator: NSProgressIndicator!
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "https://thewebpage.com.au"
self.theWebPage.mainFrame.loadRequest(NSURLRequest(URL: NSURL(string: urlString)!))
}
override func viewDidAppear() {
}
func LogIn() {
print("I logged in")
self.performSegueWithIdentifier("goToTeamPage", sender: self)
//THIS IS THE BIT THATS NOT WORKING
}
func signUp() {
print("I have to sign up now")
}
override var representedObject: AnyObject? {
didSet {
}
}
func webView(sender: WebView!, didStartProvisionalLoadForFrame frame: WebFrame!)
{
self.progressIndicator.startAnimation(self)
}
func webView(sender: WebView!, didFinishLoadForFrame frame: WebFrame!)
{
self.progressIndicator.stopAnimation(self)
}
}
You need to use a custom segue class (or possibly NSTabViewController if it’s enough for your needs). Set the segue’s type to Custom, with your class name specified:
…and implement it. With no animation, it’s simple:
class ReplaceSegue: NSStoryboardSegue {
override func perform() {
if let src = self.sourceController as? NSViewController,
let dest = self.destinationController as? NSViewController,
let window = src.view.window {
// this updates the content and adjusts window size
window.contentViewController = dest
}
}
}
In my case, I was using a sheet and wanted to transition to a different sheet with a different size, so I needed to do more:
class ReplaceSheetSegue: NSStoryboardSegue {
override func perform() {
if let src = self.sourceController as? NSViewController,
let dest = self.destinationController as? NSViewController,
let window = src.view.window {
// calculate new frame:
var rect = window.frameRectForContentRect(dest.view.frame)
rect.origin.x += (src.view.frame.width - dest.view.frame.width) / 2
rect.origin.y += src.view.frame.height - dest.view.frame.height
// don’t shrink visible content, prevent minsize from intervening:
window.contentViewController = nil
// animate resizing (TODO: crossover blending):
window.setFrame(window.convertRectToScreen(rect), display: true, animate: true)
// set new controller
window.contentViewController = dest
}
}
}

Show/Hide Window by Clicking button in Swift

I want to show/hide a window in swift by clicking a button from main window. Beginsheet is showing the window, but endsheet is not closing the window. My appdelegate code is given:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
var settingsController: SettingsController?
#IBAction func inSettings(sender: NSObject?)
{
settingsController = SettingsController(windowNibName: "SettingsController")
window.beginSheet(settingsController!.window!, completionHandler: nil)
}
#IBAction func outSettings(sender: NSObject?)
{
window.endSheet(settingsController!.window!)
}
}
SettingsController:
import Cocoa
class SettingsController: NSWindowController {
override func windowDidLoad() {
super.windowDidLoad()
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
}
}
Swift 3 Solution:
Lets say that you have WindowA and WindowB. You want to open WindowB but first You want to hide WindowA.
Connect windows with a segue. (Select "Show" as segues "Kind" property) And you need a static class to keep hidden window. in WindowA override shouldPerformSegue and keep WindowA as a static NSWindow object.
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
YourStaticClass.WindowA = self.view.window
self.view.window?.orderOut(self)
return true
}
orderOut(self) hides the window. Then WindowB will be opened.
In WindowB's view controller use a function to close windowB and show hidden WindowA:
#IBAction func btnBack_Click(_ sender: NSButton) {
YourStaticClass.WindowA?.makeKeyAndOrderFront(YourStaticClass.WindowA)
self.view.window?.close()
}
Use endSheet to end a document-modal sheet session. Like this:
#IBAction func outSettings(sender: NSObject?)
{
settingsController!.window!.endSheet(settingsController!.window!)
}
EDIT: You need to actually close the window in your completion handler you call orderOut, like this:
#IBAction func inSettings(sender: NSObject?)
{
settingsController = SettingsController(windowNibName: "SettingsController")
window.beginSheet(settingsController!.window!) {
settingsController!.window!.orderOut(nil)
}
}

Minimal Cocoa/Swift menu bar app doesn't want to run

I'm trying to build a skeleton app in Swift where I basically only have a menu bar icon, and no window. Starting from a new Storyboard project in Xcode, it worked initially, but trying to get rid of the window, it doesn't seem to want to run anymore. I have the following:
import Cocoa
import AppKit
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var window = NSWindow()
var statusBar = NSStatusBar.systemStatusBar()
var statusBarItem : NSStatusItem = NSStatusItem()
override func awakeFromNib() {
statusBarItem = statusBar.statusItemWithLength(-1)
statusBarItem.title = "Test"
}
func applicationDidFinishLaunching(aNotification: NSNotification) {
sleep(10);
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
}
In AppDelegate.swift (based on this tutorial). When running this through Xcode, I get some warnings:
2015-06-23 22:20:28.444 PENCloud[19491:3303755] Failed to connect (colorGridView) outlet from (NSApplication) to (NSColorPickerGridView): missing setter or instance variable
2015-06-23 22:20:28.444 PENCloud[19491:3303755] Failed to connect (view) outlet from (NSApplication) to (NSColorPickerGridView): missing setter or instance variable
From some Googling, it seems like I should be able to ignore these, but my statusBarItem no longer shows up. What am I missing?
You need to have main.swift with the code like below.
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
var statusBarItem : NSStatusItem!
func applicationDidFinishLaunching(aNotification: NSNotification) {
statusBarItem = statusBar.statusItemWithLength(-1)
statusBarItem.title = "Test"
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
}
autoreleasepool { () -> () in
let app = NSApplication.sharedApplication()
let delegate = AppDelegate()
app.delegate = delegate
app.run()
}
The file name MUST be main.swift. Otherwise you will get the error, Expressions are not allowed at the top level, on the line of autoreleasepool.
I found the answer here:
https://stackoverflow.com/a/26322464/338986
I have had the same problem before and I found out that when there is no storyboard or something else is missing, the app doesn't even start. I suggest setting up a new project, add your status bar code and then just do two things:
Add the "Application is UIElement (Agent)" key to your info.plist file and set it to true
Go to your storyboard, select the windowcontroller and unckeck "Initial view controller" on the right in the property inspector
If you have any other question regarding a status bar item I'd love to help you

Open New Window in Swift

I am trying to open a new window in my Swift application but I cannot get it to open.
class AppDelegate: NSObject, NSApplicationDelegate
{
func applicationDidFinishLaunching(aNotification: NSNotification)
{
openMyWindow()
}
func openMyWindow()
{
if let storyboard = NSStoryboard(name: "Main",bundle: nil)
{
if let vc = storyboard.instantiateControllerWithIdentifier("MyList") as? MyListViewController
{
var myWindow = NSWindow(contentViewController: vc)
myWindow.makeKeyAndOrderFront(self)
let controller = NSWindowController(window: myWindow)
controller.showWindow(self)
}
}
}
}
I have set the Storyboard ID in IB.
I have traced the code and it does get into the window opening code but it doesn't do anything.
BTW I do have a default storyboard entry point set and that default window opens OK but I need to have a second window open and that is what is not working.
After the openMyWindow() method is executed, the windowController will be released and consequently the window is nil. That's why it is not there.
You have to hold the window in you class to keep it alive, then the window will be visible.
var windowController : NSWindowController?

AppDelegate for Cocoa app using Storyboards in Xcode 6

I have an existing OS X app, and after converting to Storyboards as the main interface, my app delegate is no longer being used. Before, the MainMenu.xib had an "App Delegate" object, and I could set its class to my app delegate. However, the Storyboard contains no such object.
How do I get my AppDelegate back and keep storyboards? I feel like I'm missing something obvious.
If you don't specify it to be a Document-Based Application, Xcode will create an AppDelegate.swift class and connect it up in the Application Scene for you.
As of right now (Xcode Beta-2), new Document-Based apps don't come with a stub AppDelegate.swift file. Instead, there's ViewController.swift and Document.swift. Worse, the Document.swift file incorrectly instantiates the same Main.storyboard for documents.
Here's one way I got it to work:
Create an AppDelegate class (e.g.: an NSObject that adopts the NSApplicationDelegate protocol)
Drag an Object object from the Object library, into the Application Scene of Main.storyboard and set it to the AppDelegate class.
Control-drag from the Application object in the Application Scene to the AppDelegate object, and connect up its delegate.
Remove everything else from the Main.storyboard and create a new Document.storyboard for the Document window. Change the Document.swift file to instantiate that Storyboard instead of Main.
If you want to have a main application window and/or a preferences window in addition to your document windows, create an Application.storyboard and/or Preferences.storyboard for those windows, and use the AppDelegate class to instantiate them. This way, the AppDelegate can customize the main window appearance and do other handy things, including receiving IBActions sent from any window in the app.
Here's a working example of an AppDelegate.swift file for a Document-Based app that also has a separate, single main Application window, and a non-modal Preference window:
// AppDelegate.swift
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
//init() {
// super.init()
// remove this if you don't use it
//}
var application: NSApplication? = nil
func applicationDidFinishLaunching(notification: NSNotification) {
application = notification.object as? NSApplication
let path = NSBundle.mainBundle().pathForResource("Defaults", ofType: "plist")
let defaults = NSDictionary(contentsOfFile:path)
NSUserDefaults.standardUserDefaults().registerDefaults(defaults)
NSUserDefaultsController.sharedUserDefaultsController().initialValues = defaults
NSUserDefaultsController.sharedUserDefaultsController().appliesImmediately = true
}
func applicationDidBecomeActive(notification: NSNotification) {
if application?.orderedDocuments?.count < 1 { showApplication(self) }
}
//func applicationWillFinishLaunching(notification: NSNotification) {
// remove this if you don't use it
//}
func applicationWillTerminate(notification: NSNotification) {
NSUserDefaults.standardUserDefaults().synchronize()
}
func applicationShouldOpenUntitledFile(app: NSApplication) -> Bool { return false }
func applicationShouldTerminateAfterLastWindowClosed(app: NSApplication) -> Bool { return false }
var applicationController: NSWindowController?
#IBAction func showApplication(sender : AnyObject) {
if !applicationController {
let storyboard = NSStoryboard(name: "Application", bundle: nil)
applicationController = storyboard.instantiateInitialController() as? NSWindowController
if let window = applicationController?.window {
window.titlebarAppearsTransparent = true
window.titleVisibility = NSWindowTitleVisibility.Hidden
window.styleMask |= NSFullSizeContentViewWindowMask
}
}
if applicationController { applicationController!.showWindow(sender) }
}
var preferencesController: NSWindowController?
#IBAction func showPreferences(sender : AnyObject) {
if !preferencesController {
let storyboard = NSStoryboard(name: "Preferences", bundle: nil)
preferencesController = storyboard.instantiateInitialController() as? NSWindowController
}
if preferencesController { preferencesController!.showWindow(sender) }
}
}
Here's another cheap and easy way to do it, if all you want to do is customize the appearance of the main window before it appears:
Make your own subclass of NSWindowController, and connect it up as the delegate of the main window.
Implement windowDidUpdate as a hook to the window so you can set up the desired options, but also remove the window delegate so the function only gets called once. This is all the code you need to make that work:
// WindowController.swift
import Cocoa
class WindowController: NSWindowController, NSWindowDelegate {
func windowDidUpdate(notification: NSNotification!) {
if let window = notification.object as? NSWindow! {
window.titlebarAppearsTransparent = true
window.titleVisibility = NSWindowTitleVisibility.Hidden
window.styleMask |= NSFullSizeContentViewWindowMask
window.delegate = nil }
}
}
Actually, an even easier way to apply those appearance options to the window, is by using Interface Builder to add them as User Defined Runtime Attributes to the NSWindow object. You don't need to subclass NSWindowController or write any code at all. Just plug in these values to the window object via the Identity Inspector pane:
Keypath: titlebarAppearsTransparent, Type: Boolean, Value: Checked
Keypath: titleVisibility, Type: Number, Value: 1
Keypath: styleMask, Type: Number, Value: 32783
Of course, you can't specify individual bits of the styleMask, but it's easy enough to add them all together and get a single number to specify the style.
With Storyboard architecture, and the new powers given to NSViewController, there's not as much need to subclass NSWindowController anymore.

Resources