What is the best way to remember the Windows position between application loads using Obj-C? I am using Interface Builder for the interface, is it possible to do this with bindings.
What is the recommended method? Thank you.
Put a name that is unique to that window (e.g. "MainWindow" or "PrefsWindow") in the Autosave field under Attributes in Interface Builder. It will then have its location saved in your User Defaults automatically.
To set the Autosave name programmatically, use -setFrameAutosaveName:
. You may want to do this if you have a document-based App or some other situation where it doesn't make sense to set the Autosave name in IB.
Link to documentation.
In Swift:
class MainWindowController : NSWindowController {
override func windowDidLoad() {
shouldCascadeWindows = false
window?.setFrameAutosaveName("MainWindow")
super.windowDidLoad()
}
According to the doc, to save a window's position:
NSWindow *window = // the window in question
[[window windowController] setShouldCascadeWindows:NO]; // Tell the controller to not cascade its windows.
[window setFrameAutosaveName:[window representedFilename]]; // Specify the autosave name for the window.
I tried all the solutions. It can only saves the position, not the size. So we should do that manually. This is how I do it on my GifCapture app https://github.com/onmyway133/GifCapture
class MainWindowController: NSWindowController, NSWindowDelegate {
let key = "GifCaptureFrameKey"
override func windowDidLoad() {
super.windowDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(windowWillClose(_:)), name: Notification.Name.NSWindowWillClose, object: nil)
}
override func awakeFromNib() {
super.awakeFromNib()
guard let data = UserDefaults.standard.data(forKey: key),
let frame = NSKeyedUnarchiver.unarchiveObject(with: data) as? NSRect else {
return
}
window?.setFrame(frame, display: true)
}
func windowWillClose(_ notification: Notification) {
guard let frame = window?.frame else {
return
}
let data = NSKeyedArchiver.archivedData(withRootObject: frame)
UserDefaults.standard.set(data, forKey: key)
}
}
In Swift 5.2, in your NSWindowController
class:
override func windowDidLoad() {
super.windowDidLoad()
self.windowFrameAutosaveName = "SomeWindowName"
}
That's all there is to it!
Based on onmyway133's answer I wrote a RestorableWindowController
class. As long as your window controller inherits from it, position and size for your windows are restored.
import Cocoa
open class RestorableWindowController: NSWindowController {
// MARK: - Public -
open override func windowDidLoad() {
super.windowDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(windowWillClose), name: NSWindow.willCloseNotification, object: nil)
if let frame = storedFrame {
window?.setFrame(frame, display: true)
}
}
open override func awakeFromNib() {
super.awakeFromNib()
if let frame = storedFrame {
window?.setFrame(frame, display: true)
}
}
open override var contentViewController: NSViewController? {
didSet {
if let frame = storedFrame {
window?.setFrame(frame, display: true)
}
}
}
// MARK: - Private -
private var storedFrameKey: String {
String(describing: type(of: self)) + "/storedFrameKey"
}
private var storedFrame: NSRect? {
guard let string = UserDefaults.standard.string(forKey: storedFrameKey) else {
return nil
}
return NSRectFromString(string)
}
@objc private func windowWillClose() {
guard let frame = window?.frame else {
return
}
UserDefaults.standard.set(NSStringFromRect(frame), forKey: storedFrameKey)
}
}
For me, the following line in -applicationDidFinishLaunching
in the app delegate workes fine (under Catalina, macOS 10.15):
[self.window setFrameAutosaveName: @"NameOfMyApp"];
it is important that this line
[self.window setDelegate: self];
is executed before setFrameAutosaveName
in -applicationDidFinishLaunching
!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With