Mac OSX Storyboard : NSViewController 之间的通信
Mac OSX Storyboard : communicate between NSViewController
我在 OS X cocoa 应用程序项目中使用故事板,其中包含一个 SplitView 控制器和另外 2 个视图控制器 LeftViewController 和 RightViewController。
在 LeftViewController 中,我有一个显示名称数组的 tableView。 table视图的数据源和委托是 LeftViewController。
在 RightViewController 中,我只有一个显示 select 名称的居中标签。我想在右视图中显示名称 selected 在左视图中。
要配置 2 个视图控制器之间的通信,我使用 AppDelegate 并为 AppDelegate.h 中的每个控制器定义 2 属性
2 属性 使用下面的 NSInvocation 在视图控制器的 viewDidLoad 中初始化:
@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 中有相同的。
然后,如果我在 table 视图中单击一个名称,我会向代理发送一条消息,其中包含参数中的名称,代理会使用给定的名称更新 RightViewController 的标签。它工作正常,但根据苹果最佳实践,它并不好。
故事板中的 2 个视图控制器之间是否有另一种通信方式?
我已经阅读了很多 post 但没有找到 OS X.
您可以在这里下载简单的项目:http://we.tl/4rAl9HHIf1
通过在 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 对象,如下所示:
取消选中复选框 'Is initial controller',因为我们在 AppDelegate.m 中以编程方式添加它。
现在左右视图可以互通了。只需在 OMNLeftViewController.h 中定义一个名为 rightView 的 属性 :
self.leftViewController.rightView = self.rightViewController;
这是应用架构的更高级主题(如何传递数据)。
- 肮脏的快速解决方案:post
NSNotification
连同被遗忘的 representedObject
:
所有 NSViewControllers 都有一个很好的 属性 类型 ID,名为 representedObject
。这是将数据传递到 NSViewController
的方法之一。将您的标签绑定到此 属性。对于这个简单的例子,我们将设置 representedObject
一些 NSString instance
。您也可以使用复杂的对象结构。有人可以在评论中解释为什么故事板停止显示 representedObject
(在 swift 中键入安全?)
接下来我们添加通知观察器并在处理程序中设置代表对象。
@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
左视图控制器及其table:
选择更改后,我们 post 会通过我们的字符串发出通知。
@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:不要忘记挂钩 table查看数据源并在故事板中委托
为什么这个解决方案是脏的?因为一旦您的应用增长,您将最终陷入通知地狱。还将控制器视为数据所有者?我更喜欢 window controller/appdelegate 成为模型所有者。
结果:
- AppDelegate 作为模型所有者。
我们的左视图控制器将从 AppDelegate
获取数据。重要的是 AppDelegate
控制数据流并设置数据(不是视图控制器询问 AppDelegate
它是 table 内容因为你最终会陷入数据同步混乱)。我们可以使用 representedObject
再次执行此操作。设置完成后,我们重新加载 table(还有更高级的解决方案,如 NSArrayController 和绑定)。不要忘记在情节提要中挂钩 tableView。我们还修改 tableview 的委托方法 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
。它需要公开一些属性。有趣的是我如何获得所有控制器?一种方法(最好)是实例化我们自己的 window 控制器并将其记为 属性。另一种方法是向 NSApp 询问它的 windows(这里要小心 multiwindow 应用程序)。从那里我们只询问 contentViewController 并循环遍历 childViewControllers。一旦我们有了我们的控制器,我们就可以 set/bind 代表对象。
@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
结果:效果完全一样
PS: 如果你实例化自己的 window 控制器不要忘记从故事板中删除初始控制器
为什么这样更好?因为所有更改都转到模型并且模型发送触发器以重绘视图。此外,您最终会使用更小的视图控制器。
还有什么可以做的? NSObjectController
是模型对象和视图之间最好的粘合剂。它还可以防止绑定有时会发生的保留循环(更高级的主题)。 NSArrayController
等等...
注意事项:不是 XIB 的解决方案
我在 OS X cocoa 应用程序项目中使用故事板,其中包含一个 SplitView 控制器和另外 2 个视图控制器 LeftViewController 和 RightViewController。
在 LeftViewController 中,我有一个显示名称数组的 tableView。 table视图的数据源和委托是 LeftViewController。
在 RightViewController 中,我只有一个显示 select 名称的居中标签。我想在右视图中显示名称 selected 在左视图中。
要配置 2 个视图控制器之间的通信,我使用 AppDelegate 并为 AppDelegate.h 中的每个控制器定义 2 属性 2 属性 使用下面的 NSInvocation 在视图控制器的 viewDidLoad 中初始化:
@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 中有相同的。
然后,如果我在 table 视图中单击一个名称,我会向代理发送一条消息,其中包含参数中的名称,代理会使用给定的名称更新 RightViewController 的标签。它工作正常,但根据苹果最佳实践,它并不好。
故事板中的 2 个视图控制器之间是否有另一种通信方式?
我已经阅读了很多 post 但没有找到 OS X.
您可以在这里下载简单的项目:http://we.tl/4rAl9HHIf1
通过在 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 对象,如下所示:
取消选中复选框 'Is initial controller',因为我们在 AppDelegate.m 中以编程方式添加它。
现在左右视图可以互通了。只需在 OMNLeftViewController.h 中定义一个名为 rightView 的 属性 :
self.leftViewController.rightView = self.rightViewController;
这是应用架构的更高级主题(如何传递数据)。
- 肮脏的快速解决方案:post
NSNotification
连同被遗忘的representedObject
:
所有 NSViewControllers 都有一个很好的 属性 类型 ID,名为 representedObject
。这是将数据传递到 NSViewController
的方法之一。将您的标签绑定到此 属性。对于这个简单的例子,我们将设置 representedObject
一些 NSString instance
。您也可以使用复杂的对象结构。有人可以在评论中解释为什么故事板停止显示 representedObject
(在 swift 中键入安全?)
接下来我们添加通知观察器并在处理程序中设置代表对象。
@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
左视图控制器及其table: 选择更改后,我们 post 会通过我们的字符串发出通知。
@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:不要忘记挂钩 table查看数据源并在故事板中委托
为什么这个解决方案是脏的?因为一旦您的应用增长,您将最终陷入通知地狱。还将控制器视为数据所有者?我更喜欢 window controller/appdelegate 成为模型所有者。
结果:
- AppDelegate 作为模型所有者。
我们的左视图控制器将从 AppDelegate
获取数据。重要的是 AppDelegate
控制数据流并设置数据(不是视图控制器询问 AppDelegate
它是 table 内容因为你最终会陷入数据同步混乱)。我们可以使用 representedObject
再次执行此操作。设置完成后,我们重新加载 table(还有更高级的解决方案,如 NSArrayController 和绑定)。不要忘记在情节提要中挂钩 tableView。我们还修改 tableview 的委托方法 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
。它需要公开一些属性。有趣的是我如何获得所有控制器?一种方法(最好)是实例化我们自己的 window 控制器并将其记为 属性。另一种方法是向 NSApp 询问它的 windows(这里要小心 multiwindow 应用程序)。从那里我们只询问 contentViewController 并循环遍历 childViewControllers。一旦我们有了我们的控制器,我们就可以 set/bind 代表对象。
@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
结果:效果完全一样
PS: 如果你实例化自己的 window 控制器不要忘记从故事板中删除初始控制器
为什么这样更好?因为所有更改都转到模型并且模型发送触发器以重绘视图。此外,您最终会使用更小的视图控制器。
还有什么可以做的? NSObjectController
是模型对象和视图之间最好的粘合剂。它还可以防止绑定有时会发生的保留循环(更高级的主题)。 NSArrayController
等等...
注意事项:不是 XIB 的解决方案