views:

201

answers:

3

I have an interface that has an NSTextField, NSButton, and an NSView. When I type something in the textfield and press the button, I want the text to be drawn in the NSView. So far I have everything connected and working, except for the view.

How can I connect the text and the view so that every time I press the button, the text is drawn to the view?

A: 

Bindings

http://developer.apple.com/mac/library/documentation/cocoa/Conceptual/CocoaBindings/Concepts/WhatAreBindings.html#//apple_ref/doc/uid/20002372-CJBEJBHH

Azeem.Butt
That solves half of the problem, but doesn't get the value into the view that will draw it (unless you're proposing that the questioner write an IBPlugin for their custom view).
Peter Hosey
You can use bindings without Interface Builder, of course, but I agree that this doesn't really answer the OP's question.
Rob Keniger
Doesn't get the value into the view that will draw it? Thanks for giving me another reason to never install Growl.
Azeem.Butt
NSD: Bindings will get the value from the text field to the controller easily enough, but won't get it from the controller to the questioner's custom view because custom views, by default, don't have any bindings. The questioner would need to expose a property as a binding, then either bind it in IB, which would require an IBPlugin for the custom view, or (as Rob notes) bind it by sending the view a `bind::::` message.
Peter Hosey
+1  A: 

How can I connect the text and the view so that every time I press the button, the text is drawn to the view?

Views do their own drawing.

You need to give the view the string to draw, and then set the view as needing display. You'll do these in the action method that you wire the button up to.

First, your custom view class needs to have a property for the value (string, in this case) that it's going to display. From your action method, which should generally be on a controller object, send the view object a setFoo: message (assuming you named the property foo). That takes care of job one: The view now has the value to display.

Job two is even easier: Send the view a setNeedsDisplay: message, with the value YES.

That's it. The action method is two lines.

Of course, since views draw themselves, you also need your custom view to actually draw, so you need to implement the drawRect: method in that class. It, too, will be short; all you need to do is tell the string to draw itself.

Peter Hosey
Alright good, I had about half of this in place already, so that's reassuring. I added the setNeedsDisplay message to my action method. However, it's still not working, and I'm 99% sure it involves what you mention in your last paragraph, which I haven't added yet. Are you talking about calling the drawRect: method from the controller? If so, what would be the (NSRect *)dirtyRect argument I would put in?
ilovetacos
Oh, also, in my Controller class I created an instance of the NSView class that I connected to my view. I've been doing all the work to the NSView object from that instance. This is the correct way to do it, right?
ilovetacos
“Are you talking about calling the drawRect: method from the controller?” No. The opposite: Read the first paragraph. You need to *implement* `drawRect:` in the view.
Peter Hosey
“Oh, also, in my Controller class I created an instance of the NSView class that I connected to my view. I've been doing all the work to the NSView object from that instance. This is the correct way to do it, right?” Sort of. You should use Interface Builder whenever possible; it saves you a lot of work and helps you get your UI layout right. If, for some reason, you really should be creating the view yourself, then you need to add the view as a subview of another view, or set it as the content view of a window, for it to get told to draw.
Peter Hosey
I did use Interface Builder to make a Custom View, which I linked to an NSView class I created (this is the custom view class you described). I'm still unsure about the drawRect: method. My custom view class comes with one by default, and I added the drawAtPoint:withAttributes message to it. Is that what you mean by implementing it? It still doesn't redraw itself.Thanks for your help so far, I'm only about a week into Obj-c and Cocoa so I'm still trying to figure out how all the connections work. I'll be glad to post code if that'd be helpful.
ilovetacos
Yes, that's how you need to implement `drawRect:`. Have you added the view as a subview of another view, or set it as the content view of a window? The window is what sends the view a `display` message, so just having a view by itself won't display anything; you need it to be in a window's view hierarchy.
Peter Hosey
I haven't set it as anything, so I'm assuming it is a subview of the main window by default. In my custom view class I set an initial value for my string, and it does get drawn. The view just never gets redrawn it seems. I'll post my custom view and controller code below.
ilovetacos
It's not in the view hierarchy of a window unless you put it there, either in IB or in code. See the View Programming Guide: http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/CocoaViewsGuide/ and the IB User Guide: http://developer.apple.com/mac/library/documentation/DeveloperTools/Conceptual/IB_UserGuide/
Peter Hosey
Ok, I added two objects: NSWindow *mainWindow and NSView *mainContentView to my controller header and connected them in IB. Then I called: view = [[View alloc] init]; [mainWindow setContentView:mainContentView]; [mainContentView addSubview:view]; in my Controller implementation. A simple NSLog(@"%@", [view window]); still returns (null) though...
ilovetacos
Don't use `init` to create a view. Use `initWithFrame:`, and pass the frame rectangle you want the view to have. Also, check again that `mainWindow` is hooked up in the nib, and make sure you're loading the nib with the window in it (assuming it isn't the MainMenu nib). Better yet, just create the view inside of the window in IB.
Peter Hosey
A: 

For simplicity's sake I didn't mention this before, but the app also has a speech element to speak the string. This aspect of the program works fine, so just ignore any messages involving the SpeakAndDraw class (it's actually misnamed and only includes a speech method, nothing about drawing).

View.m

#import "View.h"


@implementation View

@synthesize stringToDraw;

- (id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self setAttributes];
        stringToDraw = @"Hola";
    }
    return self;
}

- (void)drawRect:(NSRect)dirtyRect {
    NSRect bounds = [self bounds];
    [self drawStringInRect:bounds];

}

- (void)setAttributes
{
    attributes = [[NSMutableDictionary alloc] init];
    [attributes setObject:[NSFont fontWithName:@"Helvetica"
                                          size:75]
                   forKey:NSFontAttributeName];

    [attributes setObject:[NSColor blackColor]
                   forKey:NSForegroundColorAttributeName];
}

- (void)drawStringInRect:(NSRect)rect
{
    NSSize strSize = [stringToDraw sizeWithAttributes:attributes];
    NSPoint strOrigin;
    strOrigin.x = rect.origin.x + (rect.size.width - strSize.width)/2;
    strOrigin.y = rect.origin.y + (rect.size.height - strSize.height)/2;
    [stringToDraw drawAtPoint:strOrigin withAttributes:attributes];
}

@end

SpeakerController.m

#import "SpeakerController.h"


@implementation SpeakerController

- (id)init
{
    speakAndDraw = [[SpeakAndDraw alloc] init];
    view = [[View alloc] init];
    [mainWindow setContentView:mainContentView];
    [mainContentView addSubview:view];
    return self;
}

- (IBAction)speakText:(id)sender
{
    [speakAndDraw setStringToSay:[text stringValue]];
    [speakAndDraw speak];
    [view setStringToDraw:[text stringValue]];
    [view setNeedsDisplay:YES];
    NSLog(@"%@", view.stringToDraw);
    NSLog(@"%@", [view window]);

}

@end
ilovetacos
Updated to reflect changes
ilovetacos
This doesn't answer your question, so you should edit into your question instead of having it here as an answer.
Peter Hosey