Search code examples
drawrectiphone-privateapinstabviewpsmtabbarcontrol

Has anyone found **legal** overrides to customize drawing of NSTabView?


BGHUDAppKit BGHUDTabView _drawThemeTab private API override now broken

For years, I have been using code originally based off of BGHUDAppKit, and found replacements for all of the private API that BGHUDAppKit overrides.

Except for one that I could not find a way to replace...

-[NSTabView _drawThemeTab:withState:inRect:]

(Note: I also use venerable PSMTabBarControl in many circumstances, so if all else fails I'll convert all my tab views to PSMTabBarControl)

Apple has now added the dark NSAppearance in 10.14 Mojave (so in ~10 years I can use it once we stop supporting High Sierra).

Whichever selfish dev at Apple writes NSTabView does not believe in making his view customizable, unlike all of the other NSControls which are customizable. Here is part of the hackish overrides for custom drawing of NSTabView:

// until we can eliminate private API _drawThemeTab:, return nil for new NSAppearance
- (id) appearance { return nil; }
- (id) effectiveAppearance  { return nil; }

-(void)_drawThemeTab:(id) tabItem withState:(NSUInteger) state inRect:(NSRect) aRect {

    NSInteger idx = [self indexOfTabViewItem: tabItem];
    int gradientAngle = 90;
    NSBezierPath *path = nil;

    aRect = NSInsetRect(aRect, 0.5f, 0.5f);

    if([self tabViewType] == NSLeftTabsBezelBorder) {

        gradientAngle = 0;
    } else if([self tabViewType] == NSRightTabsBezelBorder) {

        gradientAngle = 180;
    }

   NSColor *specialFillColor = [tabItem color];
   NSColor *outlineColor = nil;
   NSString *name = [specialFillColor description];
   // MEC - added new prefix 12/15/17 to fix white border around last segment in High Sierra
   if ( [name hasPrefix:@"NSNamedColorSpace System"] || [name hasPrefix:@"Catalog color: System controlColor"])
      specialFillColor = nil;
   else if ( [name isEqualToString: @"NSCalibratedWhiteColorSpace 0 1"] )
      [specialFillColor set];
   else
   {
      outlineColor = specialFillColor;
      specialFillColor = nil;
   }
   ... etc ...

Solution

  • It's probably preferrable to completely disable NSTabView's drawing (setting its tabViewType to NSNoTabsNoBorder), and create a custom segmented bar view to draw the selection separately (as a sibling view). This allows you to completely control the appearance, layout, and sizing of that custom implementation rather than relying on any details of NSTabView.

    Looking at the view hierarchy of an NSTabViewController, you can see that it has this same approach by using an NSSegmentedControl as a separate subview managing selection from the NSTabView.