Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I easily save the Window size and position state using Obj-C?

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.

like image 963
Luke Avatar asked Apr 29 '10 05:04

Luke


7 Answers

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.

like image 130
Nick Forge Avatar answered Nov 07 '22 23:11

Nick Forge


In Swift:

class MainWindowController : NSWindowController {
    override func windowDidLoad() {
        shouldCascadeWindows = false
        window?.setFrameAutosaveName("MainWindow")

        super.windowDidLoad()
    }
like image 27
BadmintonCat Avatar answered Nov 08 '22 00:11

BadmintonCat


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.
like image 41
Gon Avatar answered Nov 07 '22 23:11

Gon


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)
  }
}
like image 4
onmyway133 Avatar answered Nov 08 '22 01:11

onmyway133


In Swift 5.2, in your NSWindowController class:

override func windowDidLoad() {
    super.windowDidLoad()
    self.windowFrameAutosaveName = "SomeWindowName"
}

That's all there is to it!

like image 3
jcdl Avatar answered Nov 08 '22 01:11

jcdl


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)
    }

}
like image 2
Lausbert Avatar answered Nov 08 '22 01:11

Lausbert


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 !

like image 2
Gabriel Avatar answered Nov 08 '22 00:11

Gabriel