Macos Cocoa情节串连板响应器链
Cocoa应用程序的故事板似乎是一个很好的解决方案,因为我更喜欢你在iOS中找到的方法。然而,虽然将事物分解为单独的视图控制器有很多逻辑意义,但我不清楚如何将窗口控制(工具栏按钮)或菜单交互传递给关注的视图控制器。我的应用程序代理是第一个响应者,它接收菜单或工具栏操作的消息,但是,我如何访问需要将该消息发送到的视图控制器?可以深入查看视图控制器层次结构吗。如果是这样的话,由于应用程序代表是第一响应者,您如何从应用程序代表处到达目的地?你能让窗口控制器成为第一响应者吗。如果是,怎么做?在故事板上?在哪里Macos Cocoa情节串连板响应器链,macos,cocoa,swift,storyboard,nswindowcontroller,Macos,Cocoa,Swift,Storyboard,Nswindowcontroller,Cocoa应用程序的故事板似乎是一个很好的解决方案,因为我更喜欢你在iOS中找到的方法。然而,虽然将事物分解为单独的视图控制器有很多逻辑意义,但我不清楚如何将窗口控制(工具栏按钮)或菜单交互传递给关注的视图控制器。我的应用程序代理是第一个响应者,它接收菜单或工具栏操作的消息,但是,我如何访问需要将该消息发送到的视图控制器?可以深入查看视图控制器层次结构吗。如果是这样的话,由于应用程序代表是第一响应者,您如何从应用程序代表处到达目的地?你能让窗口控制器成为第一响应者吗。如果是,怎么做?在故事板上?
由于这是一个高层次的问题,它可能并不重要,但是,如果你想知道的话,我使用Swift进行这个项目 我不确定是否有一种“合适的”方法来解决这个问题,但是,我已经想出了一个解决方案,我现在将使用它。首先是一些细节
- 我的应用程序是一个基于文档的应用程序,因此每个窗口都有一个文档实例
- 应用程序使用的文档可以充当第一响应者,并转发我连接的任何操作
- 文档能够获得顶层窗口控制器,从那里我能够深入查看视图控制器层次结构,以获得我需要的视图控制器
override func windowDidLoad() {
super.windowDidLoad()
if self.contentViewController != nil {
var vc = self.contentViewController! as NSSplitViewController
var innerSplitView = vc.splitViewItems[0] as NSSplitViewItem
var innerSplitViewController = innerSplitView.viewController as NSSplitViewController
var layerCanvasSplitViewItem = innerSplitViewController.splitViewItems[1] as NSSplitViewItem
self.layerCanvasViewController = layerCanvasSplitViewItem.viewController as LayerCanvasViewController
}
}
这将获取视图控制器(它控制下面以红色列出的视图),并在窗口视图控制器中设置本地属性
现在,我可以直接在响应器链中的文档类中转发工具栏按钮或菜单项事件,从而接收我在菜单和工具栏项中设置的操作。像这样:
class LayerDocument: NSDocument {
@IBAction func addLayer(sender:AnyObject) {
var windowController = self.windowControllers[0] as MainWindowController
windowController.layerCanvasViewController.addLayer()
}
// ... etc.
}
由于LayerCanvasViewController在加载时被设置为主窗口控制器的属性,因此我可以直接访问它并调用所需的方法。我也遇到了同样的故事板问题,但只有一个没有文档的窗口应用程序。它是iOS应用程序的一个端口,也是我的第一个OS X应用程序。这是我的解决办法 首先像上面在LayerDocument中所做的那样添加iAction。现在转到界面生成器。您将看到,在WindowController的“连接到第一响应程序”面板中,IB现在添加了一个addLayer的Sent操作。将工具栏项目连接到此。(如果您查看任何其他控制器的First Responder连接,它将接收到addLayer的操作。我对此无能为力。无论如何。) 返回windowDidLoad。添加以下两行
// This is the top view that is shown by the window
NSView *contentView = self.window.contentView;
// This forces the responder chain to start in the content view
// instead of simply going up to the chain to the AppDelegate.
[self.window makeFirstResponder: contentView];
应该这样做。现在,当您单击工具栏项目时,它将直接转到您的操作。要执行查找视图控制器的操作,您需要在窗口和视图控制器中实现-supplementalTargetForAction:sender: 您可以列出可能对该操作感兴趣的所有子控制器,或使用通用实现:
- (id)supplementalTargetForAction:(SEL)action sender:(id)sender
{
id target = [super supplementalTargetForAction:action sender:sender];
if (target != nil) {
return target;
}
for (NSViewController *childViewController in self.childViewControllers) {
target = [NSApp targetForAction:action to:childViewController from:sender];
if (![target respondsToSelector:action]) {
target = [target supplementalTargetForAction:action sender:sender];
}
if ([target respondsToSelector:action]) {
return target;
}
}
return nil;
}
我自己也一直在努力解决这个问题 我认为“正确”的答案是依靠响应者链。例如,要连接工具栏项操作,可以选择根窗口控制器的第一响应程序。然后显示属性检查器。在属性检查器中,添加自定义操作(见图) 然后将工具栏项连接到该操作。(控制从工具栏项拖动到第一响应程序,并选择刚才添加的操作。) 最后,您可以转到ViewController(+10.10)或其他对象,只要它在响应程序链中,您希望在其中接收此事件并添加处理程序 或者,不在属性检查器中定义操作。您只需在ViewController中编写iAction。然后,转到工具栏项,并将控件拖动到窗口控制器的第一个响应程序,然后选择刚才添加的iAction。然后,事件将通过响应器链传播,直到被视图控制器接收 我认为这是正确的方法,无需在控制器之间引入任何额外耦合和/或手动转发呼叫 我遇到的唯一挑战是——我自己对Mac dev是新手——有时在接收到第一个事件后工具栏项本身被禁用。因此,虽然我认为这是正确的方法,但我自己也遇到了一些问题
但是我可以在另一个地点接收赛事,而不需要任何额外的耦合或体操。由于我是一个非常懒惰的人,我根据自己的想法提出了以下解决方案 的版本
#include <objc/runtime.h>
//-----------------------------------------------------------------------------------------------------------
IMP classSwizzleMethod(Class cls, Method method, IMP newImp)
{
auto methodReplacer = class_replaceMethod;
auto methodSetter = method_setImplementation;
IMP originalImpl = methodReplacer(cls, method_getName(method), newImp, method_getTypeEncoding(method));
if (originalImpl == nil)
originalImpl = methodSetter(method, newImp);
return originalImpl;
}
// ----------------------------------------------------------------------------
@interface NSResponder (Utils)
@end
//------------------------------------------------------------------------------
@implementation NSResponder (Utils)
//------------------------------------------------------------------------------
static IMP originalSupplementalTargetForActionSender;
//------------------------------------------------------------------------------
static id newSupplementalTargetForActionSenderImp(id self, SEL _cmd, SEL action, id sender)
{
assert([NSStringFromSelector(_cmd) isEqualToString:@"supplementalTargetForAction:sender:"]);
if ([self isKindOfClass:[NSWindowController class]] || [self isKindOfClass:[NSViewController class]]) {
id target = ((id(*)(id, SEL, SEL, id)) originalSupplementalTargetForActionSender)(self, _cmd, action, sender);
if (target != nil)
return target;
id childViewControllers = nil;
if ([self isKindOfClass:[NSWindowController class]])
childViewControllers = [[(NSWindowController*) self contentViewController] childViewControllers];
if ([self isKindOfClass:[NSViewController class]])
childViewControllers = [(NSViewController*) self childViewControllers];
for (NSViewController *childViewController in childViewControllers) {
target = [NSApp targetForAction:action to:childViewController from:sender];
if (NO == [target respondsToSelector:action])
target = [target supplementalTargetForAction:action sender:sender];
if ([target respondsToSelector:action])
return target;
}
}
return nil;
}
// ----------------------------------------------------------------------------
+ (void) load
{
Method m = nil;
m = class_getInstanceMethod([NSResponder class], NSSelectorFromString(@"supplementalTargetForAction:sender:"));
originalSupplementalTargetForActionSender = classSwizzleMethod([self class], m, (IMP)newSupplementalTargetForActionSenderImp);
}
// ----------------------------------------------------------------------------
@end
//------------------------------------------------------------------------------
#包括
//-----------------------------------------------------------------------------------------------------------
IMP classSwizzleMethod(类cls、方法Method、IMP newImp)
{
auto methodReplacer=类\替换方法;
自动方法设置器=方法设置实现;
IMP originalImpl=methodReplacer(cls,method_getName(method),newImp,method_getTypeEncoding(method));
if(originalImpl==nil)
originalImpl=methodSetter(方法,newImp);
返回原始值;
}
// ----------------------------------------------------------------------------
@接口NSResponder(Utils)
@结束
//------------------------------------------------------------------------------
@实现NSResponder(Utils)
//------------------------------------------------------------------------------
静态输入源提供目标存储发送器;
//------------------------------------------------------------------------------
静态id newSupplementalTargetForActionSenderImp(id self、SEL\u cmd、SEL action、id sender)
{
断言([NSStringFromSelector(_cmd)IsequalString:@“supplementalTargetForAction:sender:”);
if([self-isKindOfClass:[NSWindowController类]]| |[self-isKindOfCl]