I am brand spanking new to Cocoa programming, and am still kind of confused about how things wire together.
I need a pretty simple application that will fire off a single command (let's call it DoStuff
) whenever any point on the window is clicked. After a bit of research it looks like subclassing NSView
is the right way to go. My ClickerView.m
file has this:
- (void)mouseDown:(NSEvent *)theEvent {
NSLog(@"mouse down");
}
And I have added the View to the Window and have it stretching across the whole thing, and is properly writing to the log every time the window is clicked.
I also have my doStuff
method on my controller (this could be refactored to its own class I suppose, but for now it works):
- (IBAction)doStuff:(id)sender {
// do stuff here
}
So, how do I get mouseDown
in ClickerView
to be able to call DoStuff
in the controller? I have a strong .NET background and with that, I'd just have a custom event in the ClickerView that the Controller would consume; I just don't know how to do that in Cocoa.
edit based on Joshua Nozzi's advice
I added an IBOutlet
to my View (and changed it to subclass NSControl
):
@interface ClickerView : NSControl {
IBOutlet BoothController *controller;
}
@end
I wired my controller to it by clicking and dragging from the controller
item in the Outlets panel on the View to the controller. My mouseDown
method now looks like:
- (void)mouseDown:(NSEvent *)theEvent {
NSLog(@"mouse down");
[controller start:self];
}
But the controller isn't instantiated, the debugger lists it as 0x0, and the message isn't sent.
You could either add it as an IBOutlet like Joshua said, or you could use the delegate pattern.
You would create a Protocol that describes your delegate's methods like
@protocol MyViewDelegate
- (void)doStuff:(NSEvent *)event;
@end
then you'd make your view controller conform to the MyViewDelegate protocol
@interface MyViewController: NSViewController <MyViewDelegate> {
// your other ivars etc would go here
}
@end
Then you need to provide the implementation of the doStuff: in the implementation of MyViewController:
- (void)doStuff:(NSEvent *)event
{
NSLog(@"Do stuff delegate was called");
}
then in your view you'd add a weak property for the delegate. The delegate should be weak, so that a retain loop doesn't form.
@interface MyView: NSView
@property (readwrite, weak) id<MyViewDelegate> delegate;
@end
and then in your view you'd have something like this
- (void)mouseDown:(NSEvent *)event
{
// Do whatever you need to do
// Check that the delegate has been set, and this it implements the doStuff: message
if (delegate && [delegate respondsToSelector:@selector(doStuff:)]) {
[delegate doStuff:event];
}
}
and finally :) whenever your view controller creates the view, you need to set the delegate
...
MyView *view = [viewController view];
[view setDelegate:viewController];
...
Now whenever your view is clicked, the delegate in your view controller should be called.
First, your view needs a reference to the controller. This can be a simple iVar set at runtime or an outlet (designated by IBOutlet) connected at design time.
Second, NSControl is a subclass of NSView, which provides the target/action mechanism machinery for free. Use that for target/action style controls. This provides a simple way of setting the reference to your controller (the target) and the method to call when fired (the action). Even if you don't use a cell, you can still use target/action easily (NSControl usually just forwards this stuff along to its instance of an NSCell subclass but doesn't have to).
you can also use a selector calling method, define two properties in custom class:
@property id parent;
@property SEL selector;
set them in view controller:
graph.selector=@selector(onCalcRate:);
graph.parent=self;
and call as:
-(void)mouseDown:(NSEvent *)theEvent {
[super mouseDown:theEvent];
[_parent performSelector:_selector withObject:self];
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With