如何在基于 NSDocument 的 Cocoa 应用程序中使用 NSViewController

How to use NSViewController in an NSDocument-based Cocoa app

我对 iOS 有很多经验,但 Cocoa 让我有点困惑。我通读了 Cocoa 上的几个 Apple 文档,但仍有一些细节我无法在任何地方找到。文档似乎是在基于 NSDocument 的 Xcode 模板更新为使用 NSViewController 之前编写的,所以我不清楚我应该如何组织我的应用程序。该模板使用 NSWindow、NSViewController 创建故事板。

我的理解是我应该将 NSWindowController 或 NSWindow 子类化以引用我的模型对象,并在 makeWindowControllers() 中设置它。但是,如果我想使用 NSViewController 而不是将所有内容都放在 window 中,我还需要以某种方式在那里访问我的模型。我注意到在我的视图控制器中有一个叫做 representedObject 的东西,它看起来像是用来保存一些模型对象(然后被转换),但它总是 nil。这是如何设置的?

我发现很难正确地表述这个问题,但我想我在问什么 is:how 我在基于文档的应用程序中正确使用 NSViewController 吗?

PS:我知道 NSWindowController 通常用于管理作用于一个文档的多个 windows,所以大概如果我只需要一个 window 那么我不需要一个 NSWindowController。但是,需求可能会改变,使用 NSWindowController 可能会更好 运行,对吧?

我自己对此比较陌生,但希望我能增加一点见识。

您可以像在 ios 中一样使用视图控制器。您可以设置出口和目标等。对于基于 NSDocument 的应用程序,您可以使用视图控制器或 window 控制器,但我认为对于大多数应用程序,您最终会同时使用这两者,其中大部分逻辑都在视图控制器中。将逻辑放在最有意义的地方。例如,如果您的 nsdocument 可以有多个 window 类型,那么将视图控制器用于特定于每种类型的逻辑,并将 window 控制器用于适用于所有类型的逻辑。

表示的对象 属性 主要与 Cocoa 绑定相关联。虽然我开始熟悉绑定,但我没有足够的背景知识在这里详细介绍。但是通过绑定编程指南进行搜索可能会有所帮助。一般来说,绑定可以代替您需要在 ios 上编写的大量数据源代码。当它起作用时,它是神奇的。当它不起作用时,就像调试魔术一样。找出问题出在哪里可能是一个挑战。

我还没有深入研究故事板,但它是这样工作的:

如果您的应用必须支持 10.9 及更低版本,请创建子类 NSWindowController 的自定义

将这样的代码放入 NSDocument 子类中

- (void)makeWindowControllers
{
  CustomWindowController *controller = [[CustomWindowController alloc] init];
  [self addWindowController:controller];
}

如果您的应用程序有多个 windows 而不是将它们添加到此处或其他地方(按需加载),但不要忘记将其添加到文档数组 windowscontroller (addWindowController:)

如果您创建它们但不想显示所有 windows 然后覆盖

- (void)showWindows
{
  [controller showWindow:nil]
}

您可以随时在 window 控制器中访问您的模型

- (CustomDocument *)document
{
  return [self document];
}

在您的 window 控制器中使用绑定(window 控制器子类 + 键路径中的文档,它是 window 控制器的 属性)

[self.textView bind:@"editable"
                  toObject:self withKeyPath:@"document.readOnly"
                   options:@{NSValueTransformerNameBindingOption : NSNegateBooleanTransformerName}];

与 iOS 相比,大多数视图都在屏幕上,因此您必须依赖模式:委派、通知、事件(响应链),当然还有 MVC。

10.10 Yosemite 变化:

10.10 开始的 NSViewController 会自动添加到响应链(通常操作目标未知 | NSApp sendAction:to:from:) iOS 熟悉的所有委托,如 viewDidLoad... 终于实现了。这意味着我不再看到子类化 NSWindowCotroller 有什么大的好处。

NSDocument 子类是必需的,NSViewController 就足够了。

您可以随时在视图控制器中访问您的数据

- (CustomDocument *)document
{
  return (CustomDocument *)[[NSDocumentController sharedDocumentController] documentForWindow:[[self view] window]];
  //doesn't work if you do template approach
  //NSWindowController *controller = [[[self view] window] windowController];
  //CustomDocument *document = [controller document];
}

如果您这样做(符合 KVC/KVO),您可以按照上面的说明进行绑定。

小贴士: 在 Document 中为您的模型对象正确实现 UNDO,例如或者可耻地调用 updateChangeCount:

[[self.undoManager prepareWithInvocationTarget:self] deleteRowsAtIndexes:insertedIndexes];

不要将与 views/windows 相关的代码放入您的文档中

将您的应用拆分为多个 NSViewController,例如

- (void)prepareForSegue:(NSStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:AAPLListWindowControllerShowAddItemViewControllerSegueIdentifier]) {
        AAPLListViewController *listViewController = (AAPLListViewController *)self.window.contentViewController;

        AAPLAddItemViewController *addItemViewController = segue.destinationController;

        addItemViewController.delegate = listViewController;
    }
}

之前的代码是在 window 控制器上调用的,使用 viewcontroller 作为委托(同样仅在 10.10 之后才有可能)

我总是喜欢使用多个 XIB 而不是一个巨型 storyboard/XIB。使用以下 NSViewController 的子类并始终继承它:

#import <Cocoa/Cocoa.h>

@interface MyViewController : NSViewController

@property(strong) IBOutlet NSView *viewToSubstitute;

@end

#import "MyViewController.h"

@interface MyViewController ()

@end

@implementation MyViewController

- (void)awakeFromNib
{
  NSView *view = [self viewToSubstitute];
  if (view) {
    [self setViewToSubstitute:nil];
    [[self view] setFrame:[view frame]];
    [[self view] setAutoresizingMask:[view autoresizingMask]];
    [[view superview] replaceSubview:view with:[self view]];

  }
}

@end
  1. 使用 XIB 将 MyViewController 的子类添加到项目中。重命名 XIB
  2. 将 NSViewController 对象添加到 XIB 并更改其子类名称
  3. 将加载 XIB 名称更改为步骤 1 中的名称
  4. Link 视图替换为您要替换的视图 检查示例项目 Example Multi XIB project

通过 shapeart or lister or TextEdit

激励自己

真正的指南是使用 Hopper 并了解其他应用程序是如何完成的。

PS:您可以手动将 views/viewcontroller 添加到 responder chain 中。

PS2:如果您是初学者,请不要过度架构。对您的应用正常运行这一事实感到满意。

让我为简短回答类别添加一个简单的可复制粘贴示例;

在您的 NSDocument 子类中,当调用 makeWindowControllers 时,将 self 发送到视图控制器的表示对象:

- (void) makeWindowControllers
{ 
    NSStoryboard*                   storyboard          =   [NSStoryboard storyboardWithName: @"My Story Board" bundle: nil];
    NSWindowController*             windowController    =   [storyboard instantiateControllerWithIdentifier: @"My Document Window Controller"];
MyViewController*    myController               =   (id) windowController.contentViewController;
    [self addWindowController: windowController];
    myController.representedObject = self;
}

在 NSViewController 的 MyViewController 子类中,覆盖 setRepresentedObject 以捕获它的值,将它发送到 super,然后调用刷新视图:

- (void) setRepresentedObject: (id) representedObject
{
    super.representedObject = representedObject;
    [self myUpdateWindowUIFromContent];
}

谢谢,晚安,你完成了。