Mac OSX Storyboard:在NSViewController之间进行通信

问题描述 投票:2回答:2

我在带有SplitView控制器的OS X cocoa应用程序项目中使用storyboard,另外还有2个视图控制器LeftViewController和RightViewController。

在LeftViewController中,我有一个显示名称数组的tableView。 tableview的数据源和委托是LeftViewController。

在RightViewController中,我只有一个居中的标签,显示选择名称。我想在右侧视图中显示在左视图中选择的名称。

要配置2个视图控制器之间的通信,我使用AppDelegate并为AppDelegate.h中的每个控制器定义2属性.2属性在视图控制器的viewDidLoad中使用NSInvocation进行初始化:

@implementation RightViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // Do any additional setup after loading the view.
    id delg = [[NSApplication sharedApplication] delegate];
    SEL sel1 = NSSelectorFromString(@"setRightViewController:");
    NSMethodSignature * mySignature1 = [delg methodSignatureForSelector:sel1];
    NSInvocation * myInvocation1 = [NSInvocation
                                    invocationWithMethodSignature:mySignature1];

    id me = self;

    [myInvocation1 setTarget:delg];
    [myInvocation1 setSelector:sel1];
    [myInvocation1 setArgument:&me atIndex:2];
    [myInvocation1 invoke];
}

我在LeftViewController中也一样。

然后,如果我在表视图中单击一个名称,我会向参数中的名称发送一条消息,并且委托更新具有给定名称的RightViewController的标签。它工作正常,但根据苹果的最佳实践,它并不好。

是否有另一种方式在故事板内的2个视图控制器之间进行通信?

我已经阅读了很多帖子但是没有为OS X找到任何内容。

你可以在这里下载这个简单的项目:http://we.tl/4rAl9HHIf1

Commnunicate between view controller

objective-c macos nsviewcontroller nsstoryboard
2个回答
1
投票

这是应用程序架构的更高级主题(如何传递数据)。

  1. 肮脏的快速解决方案:将NSNotification与遗忘的representedObject一起发布:

所有NSViewControllers都有一个名为representedObject的id类型的很好的属性。这是如何将数据传递到NSViewController的方法之一。将您的标签绑定到此属性。对于这个简单的例子,我们将设置representedObject一些NSString instance。您也可以使用复杂的对象结构。有人可以在评论中解释为什么故事板停止显示representedObject(swift中的类型安全?)

bINDINGS

接下来,我们在handler中添加通知观察器和设置表示对象。

@implementation RightViewController
- (void)viewDidLoad
{
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserverForName:@"SelectionDidChange" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
        //[note object] contains our NSString instance
        [self setRepresentedObject:[note object]];
    }];
}

@end

左视图控制器及其表:一旦选择更改,我们会使用字符串发布通知。

@interface RightViewController () <NSTableViewDelegate, NSTableViewDataSource>
@end
@implementation RightViewController

- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
    return [[self names] count];
}

- (nullable id)tableView:(NSTableView *)tableView objectValueForTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row
{
    return [self names][row];
}

- (NSArray<NSString *>*)names
{
    return @[@"Cony", @"Brown", @"James", @"Mark", @"Kris"];
}

- (void)tableViewSelectionDidChange:(NSNotification *)notification
{
    NSTableView *tableView = [notification object];
    NSInteger selectedRow = [tableView selectedRow];
    if (selectedRow >= 0) {
        NSString *name = [self names][selectedRow];
        if (name) {
            [[NSNotificationCenter defaultCenter] postNotificationName:@"SelectionDidChange" object:name];
        }
    }
}

PS:不要忘记在storyboard中挂钩tableview数据源和委托

为什么这个解决方案很脏?因为一旦你的应用程序增长,你将最终陷入通知地狱。还将控制器视为数据所有者?我更喜欢窗口控制器/ appdelegate是模型所有者。

结果:

Result

  1. AppDelegate作为模型所有者。

我们的左视图控制器将从AppDelegate获取它的数据。重要的是AppDelegate控制数据流并设置数据(不是视图控制器询问AppDelegate它的表内容,因为你最终会导致数据同步混乱)。我们可以使用representedObject再次执行此操作。一旦设置,我们重新加载我们的表(有更多高级解决方案,如NSArrayController和绑定)。不要忘记在storyboard中挂钩tableView。我们还修改tableview的委托方法methos the tableViewSelectionDidChange来修改我们的模型对象(AppDelegate.selectedName)

#import "LeftViewController.h"
#import "AppDelegate.h"

@interface LeftViewController () <NSTableViewDelegate, NSTableViewDataSource>
@property (weak) IBOutlet NSTableView *tableView;
@end
@implementation LeftViewController

- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
    return [[self representedObject] count];
}

- (nullable id)tableView:(NSTableView *)tableView objectValueForTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row
{
    return [self representedObject][row];
}

- (void)setRepresentedObject:(id)representedObject
{
    [super setRepresentedObject:representedObject];
    //we need to reload table contents once 
    [[self tableView] reloadData];
}
- (void)tableViewSelectionDidChange:(NSNotification *)notification
{
    NSTableView *tableView = [notification object];
    NSInteger selectedRow = [tableView selectedRow];
    if (selectedRow >= 0) {
        NSString *name = [self representedObject][selectedRow];
        [(AppDelegate *)[NSApp delegate] setSelectedName:name];
    } else {
        [(AppDelegate *)[NSApp delegate] setSelectedName:nil];
    }
}

RightViewController,我们删除所有代码。为什么?因为我们将使用绑定AppDelegate.selectedName <--> RightViewController.representedObject

@implementation RightViewController

@end 

最后AppDelegate。它需要暴露一些属性。有趣的是我如何得到所有控制器?一种方法(最好的)是实例化我们自己的窗口控制器并将其记住为属性。另一种方法是向NSApp询问它的窗口(请注意多窗口应用程序)。从那里我们只需要询问contentViewController并循环访问childViewControllers。一旦我们有了控制器,我们就设置/绑定表示的对象。

hooks

@interface AppDelegate : NSObject <NSApplicationDelegate>
@property (nonatomic) NSString *selectedName;
@property (nonatomic) NSMutableArray <NSString *>*names;
@end

#import "AppDelegate.h"
#import "RightViewController.h"
#import "LeftViewController.h"

@interface AppDelegate () {

}

@property (weak, nonatomic) RightViewController *rightSplitViewController;
@property (weak, nonatomic) LeftViewController *leftSplitViewController;
@property (strong, nonatomic) NSWindowController *windowController;
@end

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    _names = [@[@"Cony", @"Brown", @"James", @"Mark", @"Kris"] mutableCopy];
    _selectedName = nil;
    NSStoryboard *storyboard = [NSStoryboard storyboardWithName:@"Main"
                                                         bundle:[NSBundle mainBundle]];

    NSWindowController *windowController = [storyboard instantiateControllerWithIdentifier:@"windowWC"];
    [self setWindowController:windowController];
    [[self windowController] showWindow:nil];
    [[self leftSplitViewController] setRepresentedObject:[self names]];
    [[self rightSplitViewController] bind:@"representedObject" toObject:self withKeyPath:@"selectedName" options:nil];

}

- (RightViewController *)rightSplitViewController
{
    if (!_rightSplitViewController) {
        NSArray<NSViewController *>*vcs = [[[self window] contentViewController] childViewControllers];
        for (NSViewController *vc in vcs) {
            if ([vc isKindOfClass:[RightViewController class]]) {
                _rightSplitViewController = (RightViewController *)vc;
                break;
            }
        }
    }
    return _rightSplitViewController;
}

- (LeftViewController *)leftSplitViewController
{
    if (!_leftSplitViewController) {
        NSArray<NSViewController *>*vcs = [[[self window] contentViewController] childViewControllers];
        for (NSViewController *vc in vcs) {
            if ([vc isKindOfClass:[LeftViewController class]]) {
                _leftSplitViewController = (LeftViewController *)vc;
                break;
            }
        }
    }
    return _leftSplitViewController;
}

- (NSWindow *)window
{
    return [[self windowController] window];
}


//VALID SOLUTION IF YOU DON'T INSTANTIATE STORYBOARD
//- (NSWindow *)window
//{
//    return [[NSApp windows] firstObject];
//}

@end

结果:完全相同的result2

PS:如果你实例化自己的窗口控制器不要忘记从Storyboard中删除初始控制器

为什么这样更好?导致所有更改都转到模型,模型将触发器发送到重绘视图。此外,您将最终在较小的视图控制器中。

还能做些什么? NSObjectController是模型对象和视图之间的最佳粘合剂。它还可以防止绑定有时会发生的保留周期(更高级的主题)。 NSArrayController等......

注意事项:不是XIB的解决方案


-1
投票

我通过在AppDelegate.m中添加以下代码设法得到我想要的东西:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    // Insert code here to initialize your application
    //
    NSStoryboard *storyboard = [NSStoryboard storyboardWithName:@"Main"
                                                            bundle:[NSBundle mainBundle]];

    self.windowController = [storyboard instantiateControllerWithIdentifier:@"windowController"];
    self.window = self.windowController.window;

    self.splitViewController = (NSSplitViewController*)self.windowController.contentViewController;
    NSSplitViewItem *item0 = [self.splitViewController.splitViewItems objectAtIndex:0];
    NSSplitViewItem *item1 = [self.splitViewController.splitViewItems objectAtIndex:1];
    self.leftViewController = (OMNLeftViewController*)item0.viewController;
    self.rightViewController = (OMNRightViewController*)item1.viewController;

    [self.window makeKeyAndOrderFront:self];
    [self.windowController showWindow:nil];
}

我们还需要编辑storyboard NSWindowController对象,如下所示:Define a id for the NSWindowController

取消选中“是初始控制器”复选框,因为我们在AppDelegate.m中以编程方式添加它。

Uncheck the  checkbox 'Is initial controller

现在左右视图可以进行通信。只需在OMNLeftViewController.h中定义名为rightView的属性:

    self.leftViewController.rightView = self.rightViewController;
© www.soinside.com 2019 - 2024. All rights reserved.