Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding a control/button bar to the bottom of an NSOutlineView configured as a source list

I am trying to add a button bar to the bottom of my NSOutlineView-based source list, as seen in many Mac applications (both Apple and third-party), as seen in these screenshots:

Xcode Source List FooterMail Source List Footer

To describe it textually, the control bar shares the source list's specially styled gradient background (or under Yosemite, "vibrancy") without overlapping any of the source list's content. In attempt to reproduce this effect, I have tried the following methods so far:

  1. Adding extra "padding" with the height of the button bar to the bottom of the source list's scroll view's clip view and allowing the bar to overlap the source list view. This only works if the button bar background is opaque and doesn't move the scrollbar out of the way.
  2. Using NSBoxes with their backgrounds set to the NSColor provided by the source list's backgroundColor property. This requires a lot of forced redraws to draw correctly (notably on window active/inactive states) but otherwise looks perfect. Similar behavior is seen with a custom NSView set up to draw the gradient background.

Is there any other method that can be used to achieve this? #2 is the closest I've been able to come but given the problems it brings, it's obviously not meant to be used by third-party developers.

Doing this with Vibrancy in Yosemite should be simple, being a matter of checking for Yosemite and inserting an NSVisualEffectView with vibrancy turned on. Getting it right under 10.8/10.9, on the other hand…

I could sidestep this problem entirely by using the built-in bottom bar drawing provided by NSWindow, but the color-merged approach is visually much cleaner, much more strongly associates controls with their parent panes, and seems to be the style of choice more and more often these days. If at all possible I'd like to use it in my own application.

like image 337
John Wells Avatar asked Nov 14 '14 20:11

John Wells


1 Answers

I was able to get this working by subclassing NSView and using KVO to observe color changes when the containing window's key state changes. It does not work on 10.10 for the reasons you mentioned (vibrancy), but it does work perfectly on 10.9.

The Sourcelist background colors thread on the Apple Mailing List was what solved it for me.

Interface:

#import <Cocoa/Cocoa.h>

@interface SourceListColoredView : NSView
@end

Implementation:

#import "SourceListColoredView.h"

@interface SourceListColoredView ()
@property (nonatomic, strong) NSColor *backgroundColor;
@property (nonatomic, assign, getter = isObservingKeyState) BOOL observingKeyState;
@end

@implementation SourceListColoredView

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

- (void)awakeFromNib
{
    NSOutlineView *outlineView = [[NSOutlineView alloc] init];
    [outlineView setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleSourceList];
    self.backgroundColor = [outlineView backgroundColor];
    [self addWindowKeyStateObservers];
}

- (void)addWindowKeyStateObservers
{
    if (!self.isObservingKeyState) {
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(redisplay)
                                                     name:NSWindowDidBecomeKeyNotification
                                                   object:[self window]];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(redisplay)
                                                     name:NSWindowDidResignKeyNotification
                                                   object:[self window]];
    }
    self.observingKeyState = YES;
}

- (void)redisplay
{
    [self setNeedsDisplay:YES];
}

- (void)dealloc
{
    if (self.isObservingKeyState) {
        [[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowDidBecomeKeyNotification object:[self window]];
        [[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowDidResignKeyNotification object:[self window]];
    }
}

- (void)drawRect:(NSRect)dirtyRect
{
    [_backgroundColor setFill];
    NSRectFill(dirtyRect);
}

@end

You may have to move the code in -awakeFromNib somewhere else depending on how you initialize the view.

like image 173
Andrew Avatar answered Nov 07 '22 02:11

Andrew