3

I've a NSButton with both an Image and Alternate Image. I would like the alternate image to be shown on hover. To solve this, I've extended the NSButton to show the alternate image when hovering the view. Is there a better solution to this?

My solution:

@interface HoverButton()
@property (nonatomic, strong) NSTrackingArea *trackingArea;
@property (nonatomic, strong) NSImage *imageTmp;
@end

@implementation HoverButton

-(void)mouseEntered:(NSEvent *)theEvent {
    [super mouseEntered:theEvent];

    [self updateImages];
    self.image = self.alternateImage;
}

-(void)mouseExited:(NSEvent *)theEvent {
    [super mouseExited:theEvent];

    self.image = self.imageTmp;
}

- (void)updateImages {
    self.imageTmp = self.image;
}

-(void)updateTrackingAreas
{
    if(self.trackingArea != nil) {
        [self removeTrackingArea:self.trackingArea];
    }

    int opts = (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways);
    self.trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds]
                                                     options:opts
                                                       owner:self
                                                    userInfo:nil];

    [self addTrackingArea:self.trackingArea];
}


@end
Grzegorz Adam Hankiewicz
  • 7,349
  • 1
  • 36
  • 78
dhrm
  • 14,335
  • 34
  • 117
  • 183

2 Answers2

0

I would say this is better suited to an NSButtonCell subclass. you can do it in one method and it won't apply to all NSButtons (I doubt thats what you actually want). Just set your button cell type in IB to your custom subclass.

here is some code I just wrote and tested that works:

- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
    if (_alternateImageOrKeyEquivalentFont && _bcFlags2.mouseInside) {
        // the draw bezel call is optional. maybe you don't want it
        [self drawBezelWithFrame:cellFrame inView:controlView];
        [self drawImage:_alternateImageOrKeyEquivalentFont
              withFrame:cellFrame
                 inView:controlView];
    } else {
        [super drawInteriorWithFrame:cellFrame
                              inView:controlView];
    }
}

you will need to set the showsBorderOnlyWhileMouseInside to YES probably in an init method for the cell.

Brad Allred
  • 7,323
  • 1
  • 30
  • 49
  • While this works, it will be rejected via the app store as `_alternateImageOrKeyEquivalentFont` and `_bcFlags2` are private api. – Kyle Apr 14 '14 at 17:29
  • @Zenox what makes them private exactly? they are declared right in the header file for the world to see. I don't know what the exact rules are since I dont publish on the App store; enlighten me please. – Brad Allred Apr 14 '14 at 21:05
  • 1
    This has a great answer to what is private: http://stackoverflow.com/questions/17580251/what-exactly-is-a-private-api-and-why-will-apple-reject-an-ios-app-if-one-is-us. As to this specific instance: http://imgur.com/2PFuEqM. – Kyle Apr 15 '14 at 11:18
  • @Zenox thank you, that certainly answers it :) I'll try to remember to update this answer with a suitable non-private solution when I have time. – Brad Allred Apr 15 '14 at 20:50
  • For mouseInside, you just have to implement `-mouseEntered:` and `-mouseExited:` in your `NSButtonCell` subclass and set a local `mouseInside` property. – Nick K9 May 27 '16 at 08:45
0

CustomButton.h

@interface CustomButton : NSButton
@property (getter=isMouseInside) BOOL mouseInside;
@end

CustomButton.m

@implementation CustomButton

+ (Class)cellClass
{
    return [CustomButtonCell class];
}

- (instancetype)initWithFrame:(NSRect)frame
{
    self = [super initWithFrame:frame];
    if (self)
    {
        [self commonCustomButtonInit];
    }
    return self;
}

- (void)commonCustomButtonInit
{
    NSTrackingArea *trackingArea = nil;
    trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds]
                                                options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect
                                                  owner:self
                                               userInfo:nil];

    [self addTrackingArea:trackingArea];
}


- (void)mouseEntered:(NSEvent *)event
{
    self.mouseInside = YES;
    if ([self.cell isKindOfClass:[CustomButtonCell class]])
    {
        CustomButtonCell *cell = self.cell;
        cell.mouseInside = YES;
    }
}

-(void)mouseExited:(NSEvent *)event
{
    self.mouseInside = NO;
    if ([self.cell isKindOfClass:[CustomButtonCell class]])
    {
        CustomButtonCell *cell = self.cell;
        cell.mouseInside = NO;
    }
}

@end

CustomButtonCell.h

@interface CustomButtonCell : NSButtonCell
@property (getter=isMouseInside) BOOL mouseInside;
@end

CustomButtonCell.m

@implementation CustomButtonCell
@end

Also see this answer.

Community
  • 1
  • 1
catlan
  • 25,100
  • 8
  • 67
  • 78