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