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?
Related
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
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
}
}
}
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
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).
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.