如何使用Obj-C轻松保存窗口大小和位置状态?

时间:2010-04-29 05:04:12

标签: objective-c cocoa macos

使用Obj-C记住应用程序加载之间的Windows位置的最佳方法是什么?我正在使用Interface Builder作为接口,是否可以使用绑定来执行此操作。

推荐的方法是什么?谢谢。

10 个答案:

答案 0 :(得分:31)

在Interface Builder中的Attributes下的Autosave字段中输入该窗口唯一的名称(例如“MainWindow”或“PrefsWindow”)。然后它会自动将其位置保存在用户默认值中。

要以编程方式设置自动保存名称,请使用-setFrameAutosaveName:。如果您有基于文档的应用程序或在IB中设置自动保存名称没有意义的其他情况,您可能希望这样做。

Link to documentation

答案 1 :(得分:10)

在斯威夫特:

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

        super.windowDidLoad()
    }

答案 2 :(得分:5)

根据doc,保存窗口的位置:

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.

答案 3 :(得分:3)

我尝试了所有解决方案。它只能保存位置,而不是大小。所以我们应该手动完成。这就是我在GifCapture应用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)
  }
}

答案 4 :(得分:2)

基于onmyway133的答案,我编写了一个RestorableWindowController类。只要您的窗口控制器继承自它,就可以恢复窗口的位置和大小。

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

}

答案 5 :(得分:2)

在Swift 5.2中,在您的NSWindowController类中:

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

仅此而已!

答案 6 :(得分:0)

要恢复窗口,可以在Interface Builder中设置恢复ID。这将用作密钥的一部分,框架存储在NSUserDefaults中。 - 但那并没有(总是)为我工作。

NSWindowsetFrameUsingName(_:)等来配置它,就像@BadmintonCat写的那样,你也可以手动序列化窗口位置,如果不起作用的话。

我的应用中最简单的解决方案是使用NSWindowController.windowFrameAutosaveName属性并将其设置为awakeFromNib(_:)中的内容。该单行成功影响了加载和保存。

答案 7 :(得分:0)

对Apples AutoSave和IB BS感到厌烦,它们有时会工作,有时会不工作,并且取决于System Prefs等等的标志设置。只需执行此操作,它就可以正常工作,甚至还可以记住用户的全屏状态!

-(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];
}

答案 8 :(得分:0)

对我来说,应用程序委托中的-applicationDidFinishLaunching中的以下行可以正常工作(在Catalina,macOS 10.15下):

  [self.window setFrameAutosaveName: @"NameOfMyApp"];

这一行很重要

  [self.window setDelegate: self];

setFrameAutosaveName中的{strong>之前 -applicationDidFinishLaunching前执行!

答案 9 :(得分:0)

像其他人一样,我发现以编程方式设置它是有效的...

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

但前提是你没有在 IB 中设置它! 如果你在两者中都设置了它......你就不能工作了。

顺便说一句:我是通过在代码的末尾添加“WTF”来发现这一点的,然后突然一切正常! ?