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
Related
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
}
}
}
The nib I am loading is a custom About window for my app. When the 'About' NSMenuItem is pressed I load the nib in AppDelegate in the following manner:
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var about = NSWindowController()
#IBAction func aboutClicked(sender: NSMenuItem) {
about = AboutWindow(windowNibName: "AboutWindow") as AboutWindow
NSBundle.mainBundle().loadNibNamed("AboutWindow", owner: about, topLevelObjects: nil)
}
*both the class that I want to hook up to the nib and nib itself are named 'AboutWindow'.
Once I create my NSWindowController and it's nib file, The custom class option for the nib's NSWindow does not allow me to put in the 'AboutWindow' class that I created alongside the nib.
As you can see the nib's custom class is set to NSWindow and it won't change
Any help on how to hook up this custom class and the nib is greatly appreciated.
Can you describe what the following doesn't do that you want to be done:
//
// AboutWindowController.swift
// AboutWindow
//
import Cocoa
class AboutWindowController: 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.
}
}
That was hooked up automatically by Xcode to AboutWindowController.xib, whose name I changed to AboutWindow.xib.
Next file:
//
// AppDelegate.swift
// AboutWindow
//
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
var about = NSWindowController()
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
#IBAction func aboutClicked(sender: NSMenuItem) {
about = AboutWindowController(windowNibName: "AboutWindow")
about.showWindow(self)
}
}
In IB, I dragged from the About menu item to the First Responder object to hook up the action. When I run the app and click on the About menu item, the AboutWindow displays.
I did not attempt to change any class names in IB.
Response to comment:
If I change AboutWindowController to this:
//
// AboutWindowController.swift
// AboutWindow
//
import Cocoa
class AboutWindowController: 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.
println("The About window has loaded")
}
}
I see the message printed in the console.
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?
It's a long story, but to cut it short; my first OSX app was written (on Yosemite) in Swift using a storyboard until I found out my (finished) app will not run on Mavericks. I need to run on Mavericks, so I have replaced the storyboard with NIBs.
My problem is with the segues; I was using 'sheet type' segues to show other view controllers in a sheet over the main view controller. A call to the presentViewControllerAsSheet method of NSViewController is a good replacement as it looks the same, but this API was introduced in Yosemite - so I need to work out how to do this for Mavericks.
In the action for a button on the main view, I've tried using beginSheet like this:
secondViewController = SecondViewController(nibName: "SecondViewController", bundle: nil)
self.view.window?.beginSheet(secondViewController!view.window!, completionHandler: nil)
But the second view controller's window is null at runtime. I've tried adding the new view controller as a subview to the application window but this is an unrecognised selector:
NSApplication.sharedApplication().windows[0].addSubView(secondViewController!.view)
I've search high and low for a description of how to show a sheet and all I can find is: Can a view controller own a sheet? but I'm sorry to admit I don't understand the answer. Can anybody help me with some working code? I'm beginning to worry that I'm trying to do something unusual but it looks OK on Yosemite, so how did people do this before Yosemite was released?
EDIT
I still haven't got to the solution, so I have put together a small app which shows the problems I'm having.
In AppDelegate.swift:
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
var mainViewController: FirstView!
func applicationDidFinishLaunching(aNotification: NSNotification) {
mainViewController = FirstView(nibName:"FirstView", bundle: nil)
window.contentView = mainViewController.view
mainViewController.view.frame = (window.contentView as! NSView).bounds
}
}
In FirstView.swift (associated NIB has a 'open sheet' button)
class FirstView: NSViewController {
var secondView: SecondView?
var secondWindow: SecondWinCon?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func pressButton(sender: AnyObject) {
secondView = SecondView(nibName: "SecondView", bundle: nil)!
// method 1 - this is the behaviour I want (but it only works on OSX 10.10)
// presentViewControllerAsSheet(secondView!)
// method 2 - this just creates a floating window
// self.view.addSubview(secondView!.view)
// self.view.window?.beginSheet(secondView!.view.window!, completionHandler: nil)
// method 3 - this also creates a floating window
secondWindow = SecondWinCon(windowNibName: "SecondWinCon")
self.view.window?.beginSheet(secondWindow!.window!, completionHandler: nil)
}
}
In SecondView.swift (associated NIB has a 'close' button)
class SecondView: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func dismissPressed(sender: AnyObject) {
if (presentingViewController != nil) {
presentingViewController?.dismissViewController(self)
} else {
self.view.window?.sheetParent?.endSheet(self.view.window!)
}
}
}
In SecondWinCon.swift (Associated NIB is empty)
class SecondWinCon: NSWindowController {
var secondView: SecondView?
override func windowDidLoad() {
super.windowDidLoad()
secondView = SecondView(nibName: "SecondView", bundle: nil)!
self.window?.contentView.addSubview(secondView!.view)
}
}
If method 1 is uncommented, you will see the behaviour I'm trying to emulate (remember it only works on OS X 10.10). Method 2 or 3 displays the second view, but not as a sheet.
I have the same problem, and found maybe is't an issue related to view life cycle.
When I call presentViewControllerAsSheet in viewDidLoad, sheet will not shown, and you will get this in console:
Failed to set (contentViewController) user defined inspected property on (NSWindow): presentViewController:animator:: View '''s view is not in a window/view hierarchy.
If you trigger this in viewWillAppear or viewDidAppear, it's totally no problem.
UPDATE
Okay, let's make it clear.
For this initial storyboard, NSWindowController is connected with a view controller, think this as a root view controller (RootVC).
Create another view controller desired as a sheet in storyboard (SheetVC).
in viewWillAppear or viewDidAppear of RootVC, [self presentViewControllerAsSheet: SheetVC]
The sheet will show, no additional code required.
If you get here looking for a solution, I was nearly there with method 3. The important step I had missed was to turn off "Visible At Launch" in the NSWindowController's NIB (it's an attribute of the NSWindow). In my sample code, this was in SecondWinCon.nib.
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.