【问题标题】:How to use NSViewController in an NSDocument-based Cocoa app如何在基于 NSDocument 的 Cocoa 应用程序中使用 NSViewController
【发布时间】:2015-08-31 17:45:23
【问题描述】:

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

我的理解是,我可能应该继承 NSWindowController 或 NSWindow 以引用我的模型对象,并在 makeWindowControllers() 中设置它。但是,如果我想使用 NSViewController 而不是将所有内容都放在窗口中,我还需要以某种方式访问​​我的模型。我注意到在我的视图控制器中有一个叫做代表对象的东西,它似乎是为了保存一些模型对象(然后被转换),但它总是为零。这是如何设置的?

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

PS:我知道 NSWindowController 通常意味着管理作用于一个文档的多个窗口,所以如果我只需要一个窗口,那么我就不需要 NSWindowController。但是,需求可能会发生变化,从长远来看,使用 NSWindowController 可能会更好,对吧?

【问题讨论】:

    标签: cocoa xcode-storyboard nsdocument nswindowcontroller nsviewcontroller


    【解决方案1】:

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

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

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

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

    如果您的应用有多个窗口而不是在此处或其他位置添加它们(按需加载),但不要忘记将其添加到文档窗口控制器数组(addWindowController:)

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

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

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

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

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

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

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

    将您的应用拆分为多个 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;
        }
    }
    

    以前的代码在 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 并更改其子类名称
    3. 将加载的 XIB 名称更改为步骤 1 中的名称
    4. 链接视图以替换您要替换的视图 检查示例项目Example Multi XIB project

    通过shapeartlisterTextEdit 激励自己

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

    PS:您可以手动将您的视图/视图控制器添加到responder chain

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

    【讨论】:

    • (CustomDocument *)[[NSDocumentController sharedController] documentForWindow:[[self view] window]]; 这一行缺少return 语句,并且sharedController 应该是sharedDocumentController
    • 修复丢失的回报
    【解决方案2】:

    我自己对此比较陌生,但希望我能补充一点见解。

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

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

    【讨论】:

      【解决方案3】:

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

      在您的 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];
      }
      

      谢谢,bonsoir,你完成了。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-06-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-11-07
        相关资源
        最近更新 更多