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

时间:2015-08-31 17:45:23

标签: cocoa xcode-storyboard nsdocument nswindowcontroller nsviewcontroller

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

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

我发现很难正确地提出这个问题,但我想我的问题是:如何在基于文档的应用程序中正确使用NSViewController?

PS:我知道NSWindowController通常用于管理多个窗口,这些窗口作用于一个文档,所以假设我只需要一个窗口,那么我就不需要一个NSWindowController。但是,要求可能会改变,从长远来看,使用NSWindowController可能会更好,对吗?

3 个答案:

答案 0 :(得分:14)

我没有潜入故事板,但这是如何运作的:

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

Document based app

将这样的代码放入NSDocument子类

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

如果你的应用程序有多个窗口而不是在这里或其他地方添加它们(按需加载)但不要忘记将它添加到文档windowscontroller数组中(addWindowController :)

如果你创建它们但你不想显示所有窗口,那么覆盖

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

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

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

在窗口控制器中使用绑定(windowcontroller子类+ keypath中的文档,它是窗口控制器的属性)

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

与iOS相比,大多数视图都在屏幕上,所以你必须依赖模式:委托,通知,事件(响应者链),当然还有MVC。

10.10约塞米蒂变化:

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

不要将与视图/窗口相关的代码放入文档

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

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

在windowcontroller上调用以前的代码,并将viewcontroller作为委托(再次仅在10.10之后)

我总是喜欢使用多个XIB而不是一个巨型故事板/ 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并更改其子类名称 Howto2
  3. 将加载XIB名称更改为步骤1中的名称 Howto3
  4. 链接视图以替换要替换的视图 Howto1 检查示例项目Example Multi XIB project
  5. 通过shapeartlisterTextEdit

    启发自己

    真正的指南是使用Hopper,看看其他应用是如何完成的。

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

    PS2:如果你是初学者,不要过度建筑师。对您的应用有效的事实感到高兴。

答案 1 :(得分:2)

我自己比较新,但希望我可以添加一些见解。

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

representObject属性主要与Cocoa绑定相关联。当我开始熟悉绑定时,我没有足够的背景来详细介绍这里。但是通过绑定编程指南搜索可能会有所帮助。通常,绑定可以取代您需要在ios上编写的大量数据源代码。当它工作时它是神奇的。当它不起作用时就像调试魔法一样。看到问题出在哪里可能是一个挑战。

答案 2 :(得分:0)

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

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

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

谢谢,邦索尔,你完成了。