How to use NSViewController in an NSDocument-based Cocoa app

故事扮演 提交于 2019-11-30 02:29:06

I haven't dived into storyboards but here is how it works:

If your app has to support 10.9 and lower create custom of subclass NSWindowController

Put code like this into NSDocument subclass

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

If your app has multiple windows than add them here or somewhere else (loaded on demand) but do not forget to add it to array of document windowscontroller (addWindowController:)

If you create them but you don't want to show all the windows then override

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

You can anytime access you model in your window controller

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

Use bindings in your window controller (windowcontroller subclass + document in the keypath which is a property of window controller)

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

In contrast to iOS most of the views are on screen so you have to rely on patterns: Delegation, Notification, Events (responder chain) and of course MVC.

10.10 Yosemite Changes:

NSViewController starting from 10.10 is automatically added to responder chain (generally target of the action is unknown | NSApp sendAction:to:from:) and all the delegates such as viewDidLoad... familiar from iOS are finally implemented. This means that I don't see big benefit of subclassing NSWindowCotroller anymore.

NSDocument subclass is mandatory and NSViewController is sufficient.

You can anytime access you data in your view controller

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

If you do like this (conforming to KVC/KVO) you can do binding as written above.

Tips: Correctly implement UNDO for your model objects in Document e.g. or shamefully call updateChangeCount:

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

Do not put code related to views/windows into your Document

Split your app into multiple NSViewControllers e.g.

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

Previous code is called on windowcontroller with viewcontroller as delegate (again possible only after 10.10)

I always prefer to use multiple XIBs rather than one giant storyboard/XIB. Use following subclass of NSViewController and always inherit from it:

#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. Add a subclass of MyViewController to the project with XIB. Rename the XIB
  2. Add NSViewController Object to the XIB and change its subclass name

  3. Change the loading XIB name to name from step 1

  4. Link view to substitute to the view you want to replace

    Check example project Example Multi XIB project

Inspire yourself by shapeart or lister or TextEdit

And a real guide is to use Hopper and see how other apps are done.

PS: You can add your views/viewcontroller into responder chain manually.

PS2: If you are beginner don't over-architect. Be happy with the fact that your app works.

I'm relatively new to this myself but hopefully I can add a little insight.

You can use the view controllers much as you would in ios. You can set outlets and targets and such. For NSDocument-based apps you can use a view controller or the window controller but I think for most applications you'll end up using both with most of the logic being in the view controller. Put the logic wherever it makes the most sense. For example, if your nsdocument can have multiple window types then use the view controller for logic specific to each type and the window controller for logic that applies to all the types.

The representedObject property is primarily associated with Cocoa bindings. While I am beginning to become familiar with bindings I don't have enough background to go into detail here. But a search through the bindings programming guide might be helpful. In general bindings can take the place of a lot of data source code you would need to write on ios. When it works it's magical. When it doesn't work it's like debugging magic. It can be a challenge to see where things went wrong.

Let me add a simple copy-pastable sample for the short answer category;

In your NSDocument subclass, send self to the represented object of your view controller when you are called to 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;
}

In you MyViewController subclass of NSViewController, overwrite setRepresentedObject to trap it's value, send it to super and then make a call to refresh your view:

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

Merci, bonsoir, you're done.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!