Rollovers - Need For Speeeeeeeeeeeeed!

Mikey,

I just noticed something… the lines “[super mouseEntered]; & [super mouseExited];” seem to be passing 2 triggers instead of 1 on theEvent to my Applescript (basically, my sound is playing twice).

CarbonQuark

Now go learn why it worked:

http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/index.html
http://developer.apple.com/documentation/Cocoa/Conceptual/ObjectiveC/index.html
http://developer.apple.com/documentation/Cocoa/Conceptual/ObjectiveC/Articles/chapter_3_section_3.html#//apple_ref/doc/uid/TP30001163-CH8-85882

Remember that when you override a method of a class, you replace all of what that method does. If you want to add to it, since you won’t always know what it does–and you should not necessarily care, either–you can tell the superclass to perform the original method.

Imagine you have a view that draws itself with “drawRect:”. It makes a black square. That’s all it does, to keep it simple.

- (void)drawRect:(NSRect)rect { [[NSColor blackColor] set]; NSRectFill(rect); }
You subclass this view because you want to draw a little red square inside of the black square. You don’t want or need to care how the view draws the black, but you know you want it to do that before you draw a red square.

If we override -drawRect: in a subclass of the view, writing it this way, the black won’t be drawn:

- (void)drawRect:(NSRect)rect { [[NSColor redColor] set]; NSRectFill(NSInsetRect(rect, 3, 3)); // do our extra drawing }
Here’s the drawRect: of your subclass that does both the original drawing AND your drawing:

- (void)drawRect:(NSRect)rect { [super drawRect:rect]; // do the superclass's drawing first [[NSColor redColor] set]; NSRectFill(NSInsetRect(rect, 3, 3)); // do our extra drawing }
Make sense?

Playing twice when? On enter or exit?

Comment out the calls to the superclass. Does it continue to make sounds on enter? Does it make sounds on exit?

Post your code, just to make sure we know what exactly you’re looking at.

Hi Quark & Mickey,

sorry for the confusion and many thanks for your correction, Mickey. I thougt, switching the icon was the only functionality needed.
Here an updated version that plays the button sound (as set in Interface Builder) on mouseover and correctly forwards it’s events:

[code]#import “MyRolloverButton.h”

@implementation MyRolloverButton
-(void)awakeFromNib{
[self addTrackingRect:[self bounds]
owner:self
userData:nil
assumeInside:NO];
}

  • (void)mouseEntered:(NSEvent *)theEvent{
    [self highlight:YES];
    [[self sound] play]; // or remove the line here and add it to mouseExited
    [super mouseEntered];
    }

  • (void)mouseExited:(NSEvent *)theEvent{
    [self highlight:NO];
    [super mouseExited];
    }

@end[/code]
I tried it and everything seems to work as expected. Hope that helps …

D.

Again, your tracking rect registration should be done in -viewWillMoveToSuperview, not -awakeFromNib.

Hi Mickey-San,

have you tried it? AFAIK -viewWillMoveToSuperview is only called when I create an NSView programmatically and use for example - (void)addSubview:(NSView *)aView. When using the button subclass in Interface Builder as described above -viewWillMoveToSuperview will never be called …
Do you get other results? Please show me how to … and btw. can you explain what problem of -awakeFromNib (in this context) you see?

thx

D.

Apparently, it seems to depend on the object being subclassed. Works with a straight-up NSView subclass instantiated via Interface Builder:

http://www.mikey-san.net/viewWillMoveToSuperviewTest.zip

. . . but Interface Builder seems to be changing this up with NSButton, probably due to the way it unarchives objects. (Similar to how -initWithFrame: isn’t called for a couple of objects, but -initWithCoder: is instead.)

It’s generally preferred to set up tracking rects in -viewWillMoveToSuperview or -viewDidMoveToSuperview because that’s semantically when you want the rects to be registered. What happens if you remove the object from a view and then re-add it there or somewhere else? Your -awakeFromNib method won’t be called again, so you don’t get your tracking rect.

Assuming Interface Builder’s not changing the game, it’s a good idea to be as non-context-specific as possible. You don’t know what you’re going to want to do in the future, and the more contextual specificity you add to your code, the harder it can be to debug down the line.

ok - you are right for Interface Builders ‘custom view’ (NSView) but I am pretty sure this will be the only case where the viewDidMoveToSuperview is called for nib objects. Try it with the ‘readymade’ view classes of Interface Builder … nada. Why? That’s the way nibs work. As you wrote the Interface Builder objects are unarchived from a nib, not inited and then added somewhere. If I would create an IBPalette for your NSView test class and then use an instance of this class in IB I guess it also would never be called …

Agreed - but as I wrote: ‘… in this context’ - I didn’t expect from what he wrote that CarbonQuark wants to move his buttons to other superviews during runtime. :wink:
And if you wanted to free the user of this subclass from having to take care of the tracking rect at all I think it would also make sense to update it on every frame/bounds change …?

D.

But if it’s not a ridiculously larger amount of work, why not do it the best way possible the first time? (Barring -initWithCoder: getting in the way.)

Right, you need to alter your tracking rect when the view:

  1. Changes frame.
  2. Changes bounds.
  3. Is removed from its superview. (Unregister.)

k - you are the boss … here an attempt:

[code]/* MyRolloverButton */

#import <Cocoa/Cocoa.h>

@interface MyRolloverButton : NSButton{
NSSound *mouseOverSound;
NSTrackingRectTag tag;
}

  • (void)updateTrackingRect;

@end[/code]

[code]#import “MyRolloverButton.h”

@implementation MyRolloverButton

  • (id)initWithCoder:(NSCoder *)decoder{

    tag = 0;

    self = [super initWithCoder:decoder];

    [self setPostsFrameChangedNotifications:YES];
    [self setPostsBoundsChangedNotifications:YES];
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    [nc addObserver:self selector:@selector(updateTrackingRect) name:NSViewFrameDidChangeNotification object:self];
    [nc addObserver:self selector:@selector(updateTrackingRect) name:NSViewBoundsDidChangeNotification object:self];

    mouseOverSound = [[self sound] copy];
    [self setSound:nil];

    return self;
    }

-(void)awakeFromNib{
tag = [self addTrackingRect:[self bounds]
owner:self
userData:nil
assumeInside:NO];
}

  • (void) dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [super dealloc];
    }

  • (void)viewWillMoveToSuperview{
    [self updateTrackingRect];
    }

  • (void)updateTrackingRect{
    [self removeTrackingRect:tag];
    if( ([self superview] != nil) ) {
    tag = [self addTrackingRect:[self bounds]
    owner:self
    userData:nil
    assumeInside:NO];
    }
    }

  • (void)mouseEntered:(NSEvent *)theEvent{
    [self highlight:YES];
    [mouseOverSound play];
    [super mouseEntered];
    }

  • (void)mouseExited:(NSEvent *)theEvent{
    [self highlight:NO];
    [super mouseExited];
    }

@end[/code]

Dominik & Mikey-San,

Thanks! You guys have been a great help. Everything seems to be working great with the new code. This code is a great starting point for me to delve into Obj-C.

Thanks,
CarbonQuark

Couple things:

The rollovers are still laggy (I suspect this is because of the sound). A new rollover can’t active until the sound finishes playing (my sound is very short, so it is not too laggy). Maybe there is a way to play more than 1 sound at a time and thus no delay.

Another thing, when I assign a sound to a button in the sound field of the attributes panel in interface builder and save and quit and then launch interface builder the sound disappears from the sound field. Bug in interface builder?

Thanks,
CarbonQuark

I will be away until the 14th so I will be unable to respond until then.