Cocoa NSView子类如何与控制器通信?

Cocoa NSView子类如何与控制器通信?,cocoa,macos,nsview,Cocoa,Macos,Nsview,我对Cocoa编程是一个全新的人,我仍然有点困惑事情是如何联系在一起的 我需要一个非常简单的应用程序,只要单击窗口上的任何一点,它就会发出一个命令(我们称之为DoStuff)。经过一点研究,看来子类化NSView是正确的选择。我的ClickerView.m文件包含以下内容: - (void)mouseDown:(NSEvent *)theEvent { NSLog(@"mouse down"); } 我已经将视图添加到窗口中,并将其延伸到整个窗口中,并且在每次单击窗口时都正确地写入日志

我对Cocoa编程是一个全新的人,我仍然有点困惑事情是如何联系在一起的

我需要一个非常简单的应用程序,只要单击窗口上的任何一点,它就会发出一个命令(我们称之为
DoStuff
)。经过一点研究,看来子类化
NSView
是正确的选择。我的
ClickerView.m
文件包含以下内容:

- (void)mouseDown:(NSEvent *)theEvent {
    NSLog(@"mouse down");
}
我已经将视图添加到窗口中,并将其延伸到整个窗口中,并且在每次单击窗口时都正确地写入日志

我的控制器上还有我的
doStuff
方法(我想这可以重构为它自己的类,但现在它可以工作了):

那么,如何在
ClickerView
中获得
mouseDown
,以便能够在控制器中调用
DoStuff
?我有一个强大的.NET背景,有了它,我只需要在ClickerView中有一个自定义事件,控制器就会使用它;我只是不知道用可可怎么做

根据约书亚·诺齐的建议进行编辑

我在视图中添加了一个
IBOutlet
(并将其更改为子类
NSControl
):

我通过单击并从视图上Outlets面板中的
controller
项拖动到控制器,将控制器连接到它。我的
mouseDown
方法现在看起来像:

- (void)mouseDown:(NSEvent *)theEvent {
    NSLog(@"mouse down");
    [controller start:self];
}

但是控制器没有实例化,调试器将其列为0x0,并且消息不会发送。

首先,视图需要对控制器的引用。这可以是运行时的简单iVar集,也可以是设计时连接的插座(由IBOutlet指定)


第二,是NSView的一个子类,它免费提供机械装置。将其用于目标/动作样式控件。这提供了一种简单的方法来设置对控制器(目标)的引用以及在触发时要调用的方法(操作)。即使不使用单元格,也可以轻松地使用target/action(NSControl通常只将这些内容转发到NSCell子类的实例,但不必这样做)。

您可以像Joshua所说的那样将其添加为IBOutlet,也可以使用委托模式

您可以创建一个协议来描述代理的方法,如

@protocol MyViewDelegate
    - (void)doStuff:(NSEvent *)event;
@end
然后使视图控制器符合MyViewDelegate协议

@interface MyViewController: NSViewController <MyViewDelegate> {
    // your other ivars etc would go here 
}
@end
然后在您的视图中为委托添加一个弱属性。委托应该是弱的,这样就不会形成retain循环

@interface MyView: NSView

@property (readwrite, weak) id<MyViewDelegate> delegate;

@end
最后:)每当视图控制器创建视图时,都需要设置委托

 ...
 MyView *view = [viewController view];
 [view setDelegate:viewController];
 ...

现在,无论何时单击视图,都应该调用视图控制器中的委托。

您也可以使用选择器调用方法, 在自定义类中定义两个属性:

@property id parent;
@property SEL selector;
在视图控制器中设置它们:

- (void)doStuff:(NSEvent *)event
{
    NSLog(@"Do stuff delegate was called");
}
graph.selector=@selector(onCalcRate:);
graph.parent=self;
并称为:

-(void)mouseDown:(NSEvent *)theEvent {
    [super mouseDown:theEvent];
    [_parent performSelector:_selector withObject:self];
}

好吧,我想我还是做错了。我在ClickerView中创建了一个IBMoutlet,如下所示:IBMoutlet MyController*controller;然后我将控制器连接到IB中的视图。但是当调用MouseDown事件方法时,控制器没有实例化(调试器说0x0)。我做错什么了?我不知道。你得说得更具体些。让我们从最简单的开始:你确定你的插座连接到Interface Builder中的任何东西吗?最奇怪的事情。我删除了视图,然后在Interface Builder中重新添加了它,现在控制器已实例化并正常工作。
[delegate respondsToSelector:@selector(doStuff:)]
是否有问题?由于委托类型为id,因此它没有方法,只有协议定义的方法。编译器只知道委托具有NSObject和protocol方法,但是objc运行时可以使用respondsToSelector:找出对象响应的消息。我遇到了问题,因为我的视图没有在interface builder中将
委托
显示为outlet的选项。然后我意识到我需要声明
@属性IBOutlet id delegate
,特别是作为
IBOutlet
。这对一些人来说可能是不言而喻的,但一开始我对此一无所知。我发现这个答案非常有用。为了克服Matthieu提到的问题,我将协议声明为
@protocol MyViewDelegate
,而不是
@protocol MyViewDelegate
,并且一切都运行得很好。请看这里:注意,一般来说,视图永远不应该知道它们的控制器,也不应该包含任何必要的逻辑,以便无声地表示控制器希望它们表示的内容。同样,视图不应该有自己对控制器的引用,无论是视图控制器还是其他控制器;相反,该视图的控制器应该引用这些。
@property id parent;
@property SEL selector;
graph.selector=@selector(onCalcRate:);
graph.parent=self;
-(void)mouseDown:(NSEvent *)theEvent {
    [super mouseDown:theEvent];
    [_parent performSelector:_selector withObject:self];
}