NSScrollView: Override system display settings?
Asked Answered
V

2

8

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?

Velarize answered 11/6, 2016 at 2:36 Comment(9)
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?Londrina
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.Velarize
Ignore El Tomato, he's a bit grumpy lately.Maugre
Instead of ignoring the users prefs, why don't you find a way to support the scrollers?Maugre
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.Velarize
If you don't want to see the scrollers you can set hasHorizontalScroller and hasVerticalScroller to NO.Maugre
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.Velarize
I can scroll a scrollview without scrollers using the scrollwheel on my mouse.Maugre
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.Velarize
N
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
Nally answered 5/4, 2017 at 9:49 Comment(2)
No need to swizzle at all. Just subclass NSScrollView and override scrollerStyle.Destefano
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).Nally
S
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.

Southerland answered 11/6, 2016 at 4:39 Comment(2)
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.Velarize
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.Velarize

© 2022 - 2024 — McMap. All rights reserved.