7

I have a NSScrollView, which was set to:

MyNSScrollView.hasHorizontalScroller = YES;
MyNSScrollView.hasVerticalScroller = YES;
MyNSScrollView.autohidesScrollers = YES;
MyNSScrollView.scrollerStyle = NSScrollerStyleOverlay;

I noticed that when if there's no trackpad connecting to OS X, and by default, the NSScrollView will ignore my settings in the code and force the scrollers always shown:

Settings

I can only either change my system settings to "When scrolling" or set hasHorizontalScroller etc. to NO to hide it, and the later will disable mouse scrolling which is not the result I want.

By default (Automatically based on mouse or trackpad) will always display the scroller if the user has no trackpad, even when the content size does not exceed the frame size. But if you have a trackpad, it will be overlay style that no matter the scroller shows or not, it's above the content.

The difference between the 2 is that "legacy" style will take up spaces in the scrollerview. It'd be a problem if you were relaying on the visiableRect value for calculation, or your contents needs to remain certain aspect-ratio via constraints.

Is there a way to force hide them without disabling them?

Cai
  • 3,609
  • 2
  • 19
  • 39
  • I suggest you read what you have written for yourself. "it will ignore my settings in the program and force them always shown" => What is it? Your application? What are they? scroll bars? – El Tomato Jun 11 '16 at 03:10
  • there are 2 scroller bars, horizontal and vertical, that why I said them as I didn't mention other things but those 2 bars. I'm sorry if it does not sound clear enough. – Cai Jun 11 '16 at 03:15
  • 1
    Ignore El Tomato, he's a bit grumpy lately. – Willeke Jun 11 '16 at 12:24
  • Instead of ignoring the users prefs, why don't you find a way to support the scrollers? – Willeke Jun 11 '16 at 12:24
  • As the scroller size could be different on different system, it could be too many unpredictable situations. I'm thinking about getting the scroller frame and resize it to zero width/height. – Cai Jun 11 '16 at 21:31
  • If you don't want to see the scrollers you can set `hasHorizontalScroller` and `hasVerticalScroller` to `NO`. – Willeke Jun 11 '16 at 22:19
  • Yes, but that will left the scrollview unscrollable. I need them to be scrollable but without the bars showing or taken up space of the visiableRect. – Cai Jun 11 '16 at 22:20
  • I can scroll a scrollview without scrollers using the scrollwheel on my mouse. – Willeke Jun 12 '16 at 12:53
  • When I set them to NO, scrollwheel of my Magic Mouse can no longer scroll the view. It would have been much easier if that was true. I'll test it again after WWDC. – Cai Jun 12 '16 at 12:57

2 Answers2

2

You can force the whole app to use overlay scrollers by using some low-level Objective-C magic (method swizzling):

#import <Cocoa/Cocoa.h>
#import <objc/runtime.h>

static IMP old_preferredScrollerStyle = NULL;
static NSScrollerStyle new_preferredScrollerStyle(id self, SEL _cmd) {
    // Always prefer overlay style.
    return NSScrollerStyleOverlay;
}

static IMP old_setScrollerStyle = NULL;
static void new_setScrollerStyle(id self, SEL _cmd, NSScrollerStyle style) {
    // Call old implementation but always with overlay style.
    void(*oldImp)(id self, SEL _cmd, NSScrollerStyle style)
        = (void(*)(id, SEL, NSScrollerStyle))old_setScrollerStyle;
    oldImp(self, _cmd, NSScrollerStyleOverlay);
}

/// Force the overlay style scrollers for this app.
@interface NSScrollView (ForceOverlay)
@end

@implementation NSScrollView (ForceOverlay)

+ (void)load
{
    [super load];

    // Replace the preferred style. This sets the style for app startup and new NSScroller
    // and NSScrollView instances.
    Method originalMethod = class_getClassMethod(
        [NSScroller class],
        @selector(preferredScrollerStyle)
    );
    old_preferredScrollerStyle = method_setImplementation(
        originalMethod,
        (IMP)new_preferredScrollerStyle
    );

    // Replace the NSScrollView setter. This prevents the change to the legacy style, for example
    // when the user switches the system setting.
    originalMethod = class_getInstanceMethod(
        [NSScrollView class],
        @selector(setScrollerStyle:)
    );
    old_setScrollerStyle = method_setImplementation(
        originalMethod,
        (IMP)new_setScrollerStyle
    );
}

@end
LW001
  • 2,452
  • 6
  • 27
  • 36
DarkDust
  • 90,870
  • 19
  • 190
  • 224
  • 4
    No need to swizzle at all. Just subclass `NSScrollView` and override `scrollerStyle`. –  May 03 '18 at 21:20
  • Of course. But you would need to make sure that _every_ scrollview uses you subclass. Depending on the size of your project this may be a no-brainer or a major problem (if using external components, for example). – DarkDust May 04 '18 at 06:38
1

You have not been clear about exactly what symptoms occur under what circumstances. For example, what is your normal setting for "Show scroll bars:" in that preference pane? What do you want the behavior of the scrollers to be? Always visible? Only show when scrolling?

In any case, I think the issue is that you simply are misunderstanding what autohidesScrollers does. Setting that to true simply means that the scrollers are hidden when the document view does not extend past the bounds of the clip view (a.k.a. content view). That is if there's no place to scroll to because everything is already showing.

That property has nothing to do with the scrollers being visible always or only when scrolling or whatever. That's a system setting that you can't override programmatically. All scrollers behave the same throughout all apps in the user session.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • By default (Automatically based on mouse or trackpad) will always display the scroller if the user has no trackpad, even when the content size does not exceed the frame size. But if you have a trackpad, it will be overlay style that no matter the scroller shows or not, it's above the content. The difference between the 2 is that "legacy" style will take up spaces in the scrollerview. It'd be a problem if you were relaying on the `visiableRect` value for calculation, or your contents needs to remain certain aspect-ratio via constraints. – Cai Jun 11 '16 at 06:46
  • and no i did not misunderstand what `autohidesScrollers` is for. Just that by default OS X settings, users without a trackpad will always have scrollers no matter it's scrollable or not. That's why I asked this question, as I expect it should "hide" the scroller if it does not need to be there. Only by disable scrollers in the code can complete hide those bars for mouse only users when they are not needed. – Cai Jun 11 '16 at 06:54