Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does an NSView subclass communicate with the controller?

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.

like image 804
swilliams Avatar asked Jul 31 '11 17:07

swilliams


3 Answers

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.

like image 114
iain Avatar answered Oct 28 '22 08:10

iain


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).

like image 43
Joshua Nozzi Avatar answered Oct 28 '22 08:10

Joshua Nozzi


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];
}
like image 23
roberto Avatar answered Oct 28 '22 08:10

roberto