Drawing into a custom view from another object?

Member
Posts: 22
Joined: 2007.05
Post: #1
Here is another rookie question:

How do you draw into a Custom View from another object?

I can create the initial image. But I would like to draw on top of it. So here is the custom view:

Code:
@implementation WBPTView

- (id)initWithFrame:(NSRect)frameRect
{
    if ((self = [super initWithFrame:frameRect]) != nil) {
    }
    return self;
}

- (void)drawRect:(NSRect)rect
{
    [[NSGraphicsContext currentContext] setImageInterpolation: NSImageInterpolationHigh];
    
    NSRect bounds = [self bounds];
    [[NSColor whiteColor] set];
    [NSBezierPath fillRect:bounds];
    
    [[NSColor blackColor] set];
    [NSBezierPath strokeRect:bounds];
    
    // Let's draw the table into the view.
    NSString * tableSrcfile = @"/Table.png";
    NSRect dstRectTable = NSMakeRect(0, 0, 794, 515);
    NSImage * tableImage = [[NSImage alloc] initWithContentsOfFile:tableSrcfile];
    [tableImage drawInRect:dstRectTable fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
    [tableImage release];
}

@end

But I would like to start drawing cards into it like so:

Code:
    // This is the source image - CardSheet.png. We pull the srcRect from the image and put it into the dstRect using NSCompositeCopy.    
    NSString * srcfile = @"/CardSheet.png";
    NSRect srcRect = NSMakeRect(0, 0, 51, 71);
    NSRect dstRect = NSMakeRect(325, 150, 50, 70);
    NSImage * srcCardImage = [[NSImage alloc] initWithContentsOfFile:srcfile];
    [srcCardImage drawInRect:dstRect fromRect:srcRect operation:NSCompositeCopy fraction:1.0];
    
    [srcCardImage release];

I am a little lost as to where to put that last bit. Shouldn't it be a method in my Custom View class? If so, how the hell do I call it as I never instantiated the CustomView to begin with (it just works) so I have no idea where to send the message.
Quote this message in a reply
Member
Posts: 67
Joined: 2006.06
Post: #2
You could have the NSCustomView have an NSMutableArray of card objects. Then add addCard(CardThingy* card) and removeCard(CardThingy* card)

Then have the NSView draw the card list(s) every time and base their positions off of their index in the list.

Edit: By the way your going to want to pre-load all those images. The way you have it they will be loaded every time the game draws. Load them inside that if statement in the init method (I think), or in an awakeFromNib method. You could load all the images into an NSMutableDictionary, and give them keys like "table" and "clubs ace".

Then in your card class, instead of having an NSImage you could have a string like "clubs ace". When it draws it grabs the image with that key out of the dictionary.
(doesn't look like you have a card class though)

The machine does not run without the coin.
Quote this message in a reply
destin
Unregistered
 
Post: #3
Or you can implement delegation methods.

Code:
@interface NSObject (WBPTViewDelegate)
- (int)numberOfCards;
- (id)cardAtIndex:(int)index;
@end
Quote this message in a reply
Member
Posts: 22
Joined: 2007.05
Post: #4
Still working on the Card class:

Code:
//
//  Card.h
//  WBPTDeck
//
//  Created by Mark Barros on 6/1/07.
//  Copyright 2007 Warbox Software. All rights reserved.
//

/*
The card class defines the cards in the deck. Each card object has:
1.) A suit: (spades, hearts, diamonds, clubs)
2.) A value 2 - 14
3.) A card origin X & Y - this is point of origin for the card image in the CardSheet.png file
4.) A name - an NSString with a value of "As, Ks, Qs, Js, Ts, 9s ... 4c, 3c, 2c
*/

#import <Cocoa/Cocoa.h>


@interface Card : NSObject
{
    NSString * cardName;
    NSString * cardSuit;
    int cardValue;
    int cardOriginX;
    int cardOriginY;
    
}
- (id)initWithCardName:(NSString *)newCardName suit:(NSString *)newCardSuit cardValue:(int)newCardValue cardOriginX:(int)newCardOriginX cardOriginY:(int)newCardOriginY;
- (NSString *)cardName;
- (void)setCardName:(NSString *)newCardName;
- (NSString *)cardSuit;
- (void)setCardSuit:(NSString *)newCardSuit;
- (int)cardValue;
- (void)setCardValue:(int)newCardValue;
- (int)cardOriginX;
- (void)setCardOriginX:(int)cardOriginX;
- (int)cardOriginY;
- (void)setCardOriginY:(int)cardOriginY;
@end

The idea was to have the location of the card image in the png included in the instance.

The card name could be used as the key for the dictionary (the names are As, Ks, 9c, 5h etc. etc.)
Quote this message in a reply
Member
Posts: 67
Joined: 2006.06
Post: #5
You could just make the keys cardName + " " + cardSuit. Its fine to have a reference to the Image in the card class, but make some kind of resource managing class that loads all the images and stores them with some access methods.

And I see you have an x and y stored in the card class, so thats handled.

The machine does not run without the coin.
Quote this message in a reply
Member
Posts: 22
Joined: 2007.05
Post: #6
I think what I need is some pointers as to how to redraw the table (with the new card on it) in response to an event. (button click.) In my app right now I can click a button and the card name of the next card object in the deck is sent to a text view. I am using this to test the stuff that I am writing. (I have been working with Xcode less than a month) I can draw the initial view of the table. But I am having trouble with how to draw the subsequent views with the new images. I guess the initial view loads automatically when the app runs. The problem is that I do not know the name of the View object so I can not write a method in it (like the one above for drawing the card) so that I can call it with:

Code:
[myView drawACard]

because I don't know the name of the "myView". With most objects I instantiate them myself with a name and then I can call their methods but I am really confused about this.

I suppose an easier question would be this: How would you draw anything (a static image for instance) into that view from a controller object?
Quote this message in a reply
destin
Unregistered
 
Post: #7
hypnotx Wrote:I suppose an easier question would be this: How would you draw anything (a static image for instance) into that view from a controller object?

Seriously, the best way to tackle this is with a dataSource or delegate. Look how other NSTableView, NSOutlineView and other views handle this. They retrieve their data through methods that their dataSource implements.

Consider the following view:
Code:
// CardView.m

#import "CardView.h"

@interface NSObject (CardViewDataSource)
- (int)numberOfCards;
- (id)cardAtIndex:(int)index;
@end

@implementation CardView

// NSView

- (void)drawRect:(NSRect)rect
{
    if (![dataSource respondsToSelector:@selector(numberOfCards)])
        return;
    if (![dataSource respondsToSelector:@selector(cardAtIndex:)])
        return;
    
    int numberOfCards = [dataSource numberOfCards];
    int cardIndex;
    for (cardIndex = 0; cardIndex < numberOfCards; cardIndex++) {
        id currentCard = [dataSource cardAtIndex:cardIndex];

        [self lockFocus];
        [currentCard draw]; // assume this method exists
        [self unlockFocus];
    }
}


// API

- (id)dataSource;
{
    return dataSource;
}

- (void)setDataSource:(id)theDataSource;
{
    dataSource = theDataSource;
}

@end
So in drawRect:, we check with our dataSource how many cards to display, then iterate through and display each.

Then in your controller object, you might have the following, where cardArray is an instance variable of type NSMutableArray *.
Code:
// AppController.m

#import "AppController.h"

@implementation AppController

// NSObject

- (id)init;
{
    if (![super init])
        return nil;

    cardArray = [[NSMutableArray alloc] init];

    return self;
}

- (void)dealloc;
{
    [cardArray release];
    [super dealloc];
}


// Code that modifies cardArray.


// NSObject (CardViewDataSource)

- (int)numberOfCards;
{
    return [cardArray count];
}

- (id)cardAtIndex:(int)index;
{
    return [cardArray objectAtIndex:index];
}

@end
Now obviously the two instances of this class aren't connected by default. However, you can connect them in IB once you've dragged CardView.h into your main nib window.

CardView.h has to look something like this so that IB will recognize it has a dataSource.
Code:
// CardView.h

#import <Cocoa/Cocoa.h>

@interface CardView : NSView
{
    id dataSource;
}

// API
- (id)dataSource;
- (void)setDataSource:(id)theDataSource;

@end

Hope this helps.
Quote this message in a reply
Member
Posts: 67
Joined: 2006.06
Post: #8
[myView setNeedsDisplay:YES];

The machine does not run without the coin.
Quote this message in a reply
Member
Posts: 22
Joined: 2007.05
Post: #9
Destin: I get what you are saying and that will be next. Right now, if I can just figure out how to change the content in the view by clicking a button in the UI I will be 90% there.

Stealth:
Code:
[myView setNeedsDisplay:YES]
That is what I would have expected. But myView is a class and not an object. I created it by subclassing NSView in Interface Builder and then dragging a custom view onto the window and setting the Custom Class in the Inspector to the subclass. After that when running the program it automatically draws anything in the custom class' drawRect method. I don't know the name of the instance to call the methods on. I never did:

Code:
myView * someView = [[myView alloc] init]

so I cant call setNeedsDisplay on it. Also, because I never created the instance that is being used when the program loads, I do not know how to call any of the methods that I would add to the subclass.

Is this making any sense? I hope I am explaining it correctly.
Quote this message in a reply
destin
Unregistered
 
Post: #10
hypnotx Wrote:Destin: I get what you are saying and that will be next. Right now, if I can just figure out how to change the content in the view by clicking a button in the UI I will be 90% there.

Stealth:
Code:
[myView setNeedsDisplay:YES]
That is what I would have expected. But myView is a class and not an object. I created it by subclassing NSView in Interface Builder and then dragging a custom view onto the window and setting the Custom Class in the Inspector to the subclass. After that when running the program it automatically draws anything in the custom class' drawRect method. I don't know the name of the instance to call the methods on. I never did:

Code:
myView * someView = [[myView alloc] init]

so I cant call setNeedsDisplay on it. Also, because I never created the instance that is being used when the program loads, I do not know how to call any of the methods that I would add to the subclass.

Is this making any sense? I hope I am explaining it correctly.

So add an outlet to your controller and connect them. The easiest way to do this, I think, once the files have been already created is to add "IBOutlet MyView *theView;" to the header file, then drag the header file to the nib. Then you can control drag from the instance of your controller to the view and connect them.
Quote this message in a reply
Member
Posts: 22
Joined: 2007.05
Post: #11
Destin: BINGO! Thank you brother. That was what I needed to be able to move on. Still not comprehending some of the basics I guess. But I am getting there. Got some test code in there. Everything is cool but it draws my card on the button press then immediately erases it. I am sure I will be able to figure this one out though. Thanks again.
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  Objective-C: drawing a custom class to a custom view GryphonClaw 1 4,127 Dec 10, 2004 03:32 AM
Last Post: GryphonClaw