How to restore window position in an OSX application? - swift

I created a storyboard it has window view controller as initial view controller. I gave the window an autosave name preferencesWindow. In the preferences I checked [x] Restorable and [x] Release when closed.
When I go into the menu and click Preferences I load the window controller like so:
let storyboard = NSStoryboard(name: "Preferences", bundle: nil)
let windowController = storyboard.instantiateInitialController() as? NSWindowController
let window = windowController?.window
windowController!.showWindow(self)
This will present the preferences view controller and when I drag it to another position and click the close button it will close. So far so good. However when I load the window again from the menu, it shows on it's original position instead of the position I last dragged the window to. Why is this?
Answer
It appears to be a bug in xCode 7 setting the auto save name in code solved it.
let storyboard = NSStoryboard(name: "Preferences", bundle: nil)
let windowController = storyboard.instantiateInitialController() as? NSWindowController
let window = windowController?.window
window!.setFrameAutosaveName("preferences")
windowController!.showWindow(self)

This is a bug in Xcode 6 and I don't know if it is fixed in Xcode 7.
Setting autosave in InterfaceBuilder has no effect. To get it to work just set its name in windowDidLoad() of your windowController:
class MyWindowController: NSWindowController {
override func windowDidLoad() {
super.windowDidLoad()
self.windowFrameAutosaveName = "position"
}
}

Swift 4:
final class MyWindowController: NSWindowController {
override func windowDidLoad() {
super.windowDidLoad()
self.windowFrameAutosaveName = NSWindow.FrameAutosaveName(rawValue: "myWindow")
}
}

Related

How do I pop out a ViewController(like a modal) programmatically?

All:
I am doing a swift , cocoa macos app project with multiple ViewControllers in one storyboard.
I know if I do segue and link the segue from second ViewController to first ViewController.
I can pop out ViewController.
But what if I have a function and want to call another ViewController to present programmatically from first ViewController?
I search a lot of examples start with UIStoryBoard, but my Storyboard is NSStoryboard.
Could anyone hint me a little to start with?
my code:
func checkPassword(SystemMsg:String) -> String{
//print("x")
let storyBoard = NSStoryboard(name: "Main", bundle: nil)
let mainViewController : NSViewController = storyBoard.instantiateController(withIdentifier: "PasswordInput") as! NSViewController
//self.present(mainViewController, asPopoverRelativeTo: <#T##NSRect#>, of: sender, preferredEdge: NSRectEdge, behavior: <#T##NSPopover.Behavior#>)
return ""
}
And my viewController in storyboard look like(no segue,no link):
enter image description here
If anyone can guide me through this step by step would be appreciated.
I figure it out myself.
The most simple way contains 4 steps:
Identify your main ViewController and second ViewController in storyboard
Create your main ViewController with instantiateController
Create your second ViewController with instantiateController
use presentAsModalWindow or presentAsSheet to present secondController on main ViewController
first we need to Identify storyboard correctly:
Identify first storyboard , we need to click top blue cube and then name it in Storyboard Id area
Identify second storyboard , we need to click top blue cube and then name it in Storyboard Id area
example code:
func checkPassword(SystemMsg:String) -> String{
//print("x")
//let storyBoard = NSStoryboard(name: "Main", bundle: nil)
let mainViewController : NSViewController = self.storyboard?.instantiateController(withIdentifier: "MainController") as! NSViewController
let passwordInputViewController : NSViewController = self.storyboard?.instantiateController(withIdentifier: "PasswordInput") as! NSViewController
mainViewController.presentAsModalWindow(passwordInputViewController)
//Or
mainViewController.presentAsSheet(passwordInputViewController)
return ""
}
If I miss something please correct me gently.
reference: Transitioning between view controller, OS X
PS. if you want to pass value between ViewController, this is good reference : https://learnappmaking.com/pass-data-between-view-controllers-swift-how-to/#back-properties

Create a Window on a status bar app for macOS

Warning: macOS dev beginner here.
I have a menu bar app (with no dock). Most of the app's functionality is in the menu (and implementation is in AppDelegate), but I need a separate window that will open once I click one of the menu items.
I want to use SwiftUI, Swift 5, Xcode 11.3.
I haven't found an appropriate way to do this. Which files and similar need to be created? How to open this window programatically?
#objc func openPreferences() {
// open a new window here...
}
You have to create a window programatically. I have attached sample code of one of my apps:
private var windowController: NSWindowController?
fileprivate func createWindow()
{
let storyboard = NSStoryboard(name: "Main", bundle: nil)
self.windowController = storyboard.instantiateInitialController() as? NSWindowController
// This is example code to show how to customize the hosted view controller. You can pass additional arguments here (may an important global variables that is declared in the AppDelegate).
if let contentController = windowController?.contentViewController as? MyWindowViewController
{
// Do some assignments here
// contentController.variable = ....
// self.windowViewController = contentController // Maybe save for later use.
}
}
#objc fileprivate func open()
{
if self.windowViewController == nil
{
self.createWindow()
}
self.windowController?.showWindow(self)
NSApp.activate(ignoringOtherApps: true) // Bring window to front.
}
I have linked the open() function to a button call (hence the #objc keyword). I think that you already did this, so my open() function would be your openPreferences function.

windowWillClose and button action not called Swift

I'm designing a mac app with Xcode 10 (beta) and I got an issue with the Preference Window Controller
I have in my Main.storyboard a NSWindowController of custom class PreferenceWindowController with a toolbar. Here are its connections :
Here is the full class :
class PreferenceWindowController: NSWindowController, NSWindowDelegate {
#IBAction func didClickAuthor(_ sender: Any) {
print("author")
}
#IBAction func didClickTypo(_ sender: Any) {
print("typo")
}
override func windowDidLoad() {
super.windowDidLoad()
}
func windowWillClose(_ notification: Notification) {
print("willClose")
}
}
The window is initiated via the AppDelegate class with this code :
let storyboard = NSStoryboard(name: "Main",bundle: nil)
if let wc = storyboard.instantiateController(withIdentifier: "PreferenceWindowController") as? PreferenceWindowController
{
wc.showWindow(self)
}
The window opens as expected, with the toolbar clickable, but no functions from PreferenceWindowController are called at all, neither the closing of the window, nor the clicks on the toolbar.
I checked every connections, every class name, and I really don't know what's wrong...
SOLUTION
The solution is to store the PreferenceViewController class inside the AppDelegate class as a variable.
My solution :
var preferenceWindowController:PreferenceWindowController? = nil
#IBAction func clickPreferences(_ sender: Any) {
if let wc = storyboard.instantiateController(withIdentifier: "PreferencesWindowController") as? PreferenceWindowController {
let window = wc.window
preferenceWindowController = wc
wc.showWindow(self)
}
}
Thank you for helping !
The comment above seems like it could be on the right track. Based on the code context you've included in your question, it looks like the window controller you create will only have a lifetime for that function call.
Try making the window controller an instance variable. This is normally how I wire things up in an App delegate that creates window controllers. It's a simple pattern that works well.

Relaunch view when app is closed

I am creating a status bar app for Mac with a settings view.
I have created a NSMenuItem to launch the settings but I don't find any solutions to launch this view.
What I have tried:
NSWorkspace.shared().launchApplication("AppName")
and
StatusMenuController.swift
func showSettings() {
var mainWindow: MainWindowController!
mainWindow = MainWindowController()
mainWindow.showWindow(nil)
}
MainWindowController.swift
override func windowDidLoad() {
super.windowDidLoad()
self.window?.center()
self.window?.makeKeyAndOrderFront(nil)
NSApp.activate(ignoringOtherApps: true)
}
I saw this problem was faced in many cases and I found a solution that I don't completely understand but that I'll try to explain.
Here is how documents are managed in macOS :
From my experience, you need to check the box "Create Document-Based Application" when creating a new Mac app to have a NSDocument class (You can also add it later) which will handle how your views are being showed or not.
Adding this NSDocument class to my project made the following code work (and wasn't before) :
let storyboard = NSStoryboard(name: "Main", bundle: nil)
var windowController: NSWindowController!
windowController = storyboard.instantiateController(withIdentifier: "ScanWindowController") as! NSWindowController
windowController.showWindow(nil)

What is the correct way to make a NSWindowController Singleton in Swift?

I have a sample project as:
https://github.com/ericgorr/nspanel_show.git
My project is a storyboard, document based application. I would like to use a custom segue to toggle the visible state of the inspector window. What I have should work, but I cannot quite determine how to make the inspector window a singleton.
I believe I should start with:
class InspectorWindowController: NSWindowController
{
static let sharedInstance = InspectorWindowController()
// override func init()
// {
//
// }
override func windowDidLoad()
{
super.windowDidLoad()
NSLog( ":::: %#", InspectorWindowController.sharedInstance );
}
}
But exactly what the initialization should look like in my situation is escaping me, especially since the window is inside of a storyboard.
You can select the window controller from the window controller scene and in the attributes inspector select Single from the pop up under Presentation. This will ensure the show segue only uses a single instance of the window controller. See this answer for more information.
Here's how I would modify your code:
In Main.storyboard give your InspectorWindowController an identifier, such as "Inspector Window Controller"
In InspectorWindowController, implement your singleton as follows:
static let shared: InspectorWindowController = {
let storyboard = NSStoryboard(name:"Main", bundle: nil)
let controller = storyboard.instantiateController(withIdentifier: "Inspector Window Controller")
return controller as! InspectorWindowController
}()
In Main.storyboard delete the segue from WindowController to InspectorWindowController
In WindowController replace the showMyPanel() and hideMyPanel() IBActions with:
#IBAction func toggleInspectorPanel( _ sender: AnyObject ) {
let inspectorWindow = InspectorWindowController.shared.window!
if inspectorWindow.isVisible {
inspectorWindow.orderOut(self)
} else {
inspectorWindow.makeKeyAndOrderFront(self)
}
}
Also in WindowController, remove the NSLog() call from windowDidLoad(). It causes a recursive call to the InspectorWindowController.shared initialization code.
In Main.storyboard link the Inspector toolbar button to toggleInspectorPanel()
The InspectorWindowController.shared singleton will be initialized, and the inspector panel loaded (but not shown), the first time it is referenced.