如何从可恢复的 NSWindow 中排除某些 AppKit 视图?

How to exclude certain AppKit views from restorable NSWindow?

可以制作 NSWindows restorable,以便在应用程序启动之间保留其配置。

https://developer.apple.com/documentation/appkit/nswindow/1526255-restorable

Windows should be preserved between launch cycles to maintain interface continuity for the user. During subsequent launch cycles, the system tries to recreate the window and restore its configuration to the preserved state. Configuration data is updated as needed and saved automatically by the system.

在新的 macOS 项目中,Storyboard 上的 NSWindow 默认为 restorable


在 NSWindow 中嵌入 NSTabViewController 时出现问题。

NSTabView 自动继承了 window 的 restorable 状态,没有添加任何代码。

这使得 selected 选项卡 在应用程序启动之间持续存在。我不想要那个。我希望它始终默认为索引 0。如果恢复了 selected 选项卡,尝试在 viewDidLoad 中以编程方式 select 选项卡会产生意外结果。


如何强制将某些 AppKit UI 元素从 NSWindow 状态恢复中排除?

我希望选项卡视图不可恢复。

但我保留其他可恢复的好处,例如恢复之前设置的window大小。

如何从 NSWindow 状态恢复中排除单个视图?

AFAIK,您不能将 UI 的某个部分排除在可恢复之外。对于所有元素,它是 ON 或 OFF 的东西。这就是为什么我很少使用 Apple 自己的可恢复性 API,因为它们通常不可靠。我总是自己进行修复以获得您需要的精细控制。但是,为了更简单windows,我让系统进行恢复。

在这个序言之后,为了真正回答你的问题,我很少使用 viewDidLoad() 来设置任何 windows,因为你发现这会产生一些令人讨厌的后果(例如 window 可能还不存在!)。我总是在 viewWillAppear() 中这样做。为此,您需要进行以下设置:

  1. 你需要有一个ivar(让我们称之为tabViewController)到你的父NSTabViewController实例NSViewController(让我们称之为NSViewMainController)

  2. 覆盖 NSViewMainController 中的 prepare(for segue: NSStoryboardSegue, sender: Any?) 并像这样设置 NSTabViewController 及其 NSViewController 子级:

    override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
       // set up the tabViewController ivar
       self.tabViewController = segue.destinationController as? NSTabViewController
    
       // set up the child NSViewControllers if you need to access them via their parent (otherwise this step is not needed)
       if let childControllers = tabViewController?.children {
          for controller in childControllers {
             if let controller = controller as? NSViewController1 {
                childController1 = controller
             }
             else if let controller = controller as? NSViewController2 {
                childController2 = controller
             }
             else if let controller = controller as? NSViewController3 {
                childController3 = controller
             }
          }
       }
    }
    
  3. 覆盖 NSViewMainController 的 viewWillAppear() 然后设置想要的 tabView:

    guard let controller = tabViewController else { return }
    controller.selectedTabViewItemIndex = 0
    

主要警告:注意 viewWillAppear(),但是...与 viewDidLoad() 不同,此覆盖可以多次调用,因此您需要在代码中考虑到这一点并做出反应适当地。

状态恢复的关键是NSResponder方法restoreStateWithCoder:

This method is part of the window restoration system and is called at launch time to restore the visual state of your responder object. The default implementation does nothing but specific subclasses (such as NSView and NSWindow) override it and save important state information. Therefore, if you override this method, you should always call super at some point in your implementation.

https://developer.apple.com/documentation/appkit/nsresponder/1526253-restorestate

所以,为了恢复某个控制,使这个方法成为空操作。

它说你“应该总是调用 super”,但这会恢复 window 状态。所以如果你不想 window 恢复,就不要调用 super.

在选项卡视图的情况下,显然必须在 NSTabView(子类)本身中完成。在其他视图中,在视图控制器上重写此方法可能会起作用。

class SomeTabView: NSTabView {
    
    override func restoreState(with coder: NSCoder) {
        // Do NOT restore state
    }
    
}