Question

How can I save the window size and location of a Swift macOS app for restart?

Answer and Explanation

To save and restore the window size and location of a Swift macOS app for restart, you can leverage UserDefaults and NSWindowDelegate. Here's a comprehensive guide:

1. Implement NSWindowDelegate:

Your main window controller should conform to NSWindowDelegate to receive notifications about window events.

2. Save Window Frame to UserDefaults:

Implement the windowWillClose(_:) and windowDidMove(_:) delegate methods to save the window's frame (position and size) to UserDefaults whenever the window is moved or closed. Saving on windowDidMove(_:) ensures the position is saved even without closing.

Example code:

import Cocoa

class WindowController: NSWindowController, NSWindowDelegate {
  override func windowDidLoad() {
    super.windowDidLoad()
    self.window?.delegate = self
    restoreWindowFrame()
  }

  func windowWillClose(_ notification: Notification) {
    saveWindowFrame()
  }

  func windowDidMove(_ notification: Notification) {
    saveWindowFrame()
  }

  func saveWindowFrame() {
    guard let frame = self.window?.frame else { return }
    UserDefaults.standard.set(NSRectToData(frame), forKey: "MainWindowFrame")
  }

  func restoreWindowFrame() {
    guard let data = UserDefaults.standard.data(forKey: "MainWindowFrame"),
          let frame = NSRectFromData(data) else { return }
    self.window?.setFrame(frame, display: true)
  }

  // Helper functions to convert NSRect to Data and vice versa
  func NSRectToData(_ rect: NSRect) -> Data {
    return NSData(bytes: &rect, length: MemoryLayout.size(ofValue: rect)) as Data
  }

  func NSRectFromData(_ data: Data) -> NSRect? {
    var rect = NSRect()
    if data.count == MemoryLayout.size(ofValue: rect) {
        (data as NSData).getBytes(&rect, length: MemoryLayout.size(ofValue: rect))
        return rect
    }
    return nil
  }
}

3. Restore Window Frame on App Launch:

In your applicationDidFinishLaunching(_:) method in the AppDelegate, retrieve the saved frame from UserDefaults and set the window's frame accordingly. Call restoreWindowFrame() in your WindowController's windowDidLoad() method.

4. Handling Initial Launch:

On the very first launch (when no frame is saved yet), the restoreWindowFrame() method will simply return without changing the window's frame. You might want to set a default frame in your windowDidLoad() if no saved frame exists.

5. Additional Considerations:

- Multiple Windows: If you have multiple windows, use different keys for each window in UserDefaults (e.g., "Window1Frame", "Window2Frame").

- Screen Changes: If the user changes screen configuration (e.g., adds or removes displays), the saved frame might be off-screen. You can check if the restored frame is on a screen and adjust it if necessary using NSScreen.screens and frame.intersects(screen.frame).

- User Defaults Key: Make sure the key used in UserDefaults is unique to your application to avoid conflicts with other apps.

By implementing these steps, your macOS app will remember and restore its window size and location across restarts, providing a better user experience for the user.

More questions