Question
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.