开发者

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 reco开发者_JAVA技巧mmended 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 !


In order to restore a window, you can set the Restoration ID in Interface Builder. This will be used as part of the key under which the frame is stored in NSUserDefaults. -- but that didn't (always) work for me.

NSWindow has setFrameUsingName(_:) etc. to configure this, like @BadmintonCat wrote, and you can serialize the window position manually, too, in case that doesn't work, either.

The simplest solution in my app though was to use the NSWindowController.windowFrameAutosaveName property and set it to something in awakeFromNib(_:). That single line affected loading and saving successfully.


Got sick and tired of Apples AutoSave and IB BS which sometimes does and sometimes doesn't work and depends on flag settings in System Prefs blah blah blah. Just do this, and it ALWAYS WORKS and even remembers users full screen state!

-(void)applicationDidFinishLaunching:(NSNotification *)notification
{
    [_window makeKeyAndOrderFront:self];

    // Because Saving App Position and Size is FUBAR
    NSString *savedAppFrame = [userSettings stringForKey:AppScreenSizeAndPosition];
    NSRect frame;
    if(savedAppFrame) {
        frame = NSRectFromString(savedAppFrame);
        [_window setFrame:frame display:YES];
    }
    else
        [_window center];

    // Because saving of app size and position on screen doesn't remember full screen
    if([userSettings boolForKey:AppIsFullScreen])
        [_window toggleFullScreen:self];
}
-(void)windowDidEnterFullScreen:(NSNotification *)notification
{
    [userSettings setBool:YES forKey:AppIsFullScreen];
}
-(BOOL)windowShouldClose:(NSWindow *)sender
{
    // Have to use this to set zoom state because exit full screen state always called on close
    if(sender == _window) {
        [userSettings setBool:(_window.isZoomed ? YES:NO) forKey:AppIsFullScreen];
    }
    return YES;
}
-(void)applicationWillTerminate:(NSNotification *)aNotification
{
    [userSettings setObject:NSStringFromRect(_window.frame) forKey:AppScreenSizeAndPosition];
    [userSettings synchronize];
}


like everyone else I found that setting it programmatically works...

self.windowFrameAutosaveName = NSWindow.FrameAutosaveName("MyWindow")

but ONLY if you do NOT also set it in IB! If you set it in both... you're back to not working.

BTW: I found this out by literally adding "WTF" to the end of the one in code, and suddenly having everything working!

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜