11

Is there a simple way to disable scrolling of an NSTableView.

It seems there isn't any property on [myTableView enclosingScrollView] or [[myTableView enclosingScrollView] contentView] to disable it.

erkanyildiz
  • 13,044
  • 6
  • 50
  • 73

5 Answers5

20

This works for me: subclass NSScrollView, setup and override via:

- (id)initWithFrame:(NSRect)frameRect; // in case you generate the scroll view manually
- (void)awakeFromNib; // in case you generate the scroll view via IB
- (void)hideScrollers; // programmatically hide the scrollers, so it works all the time
- (void)scrollWheel:(NSEvent *)theEvent; // disable scrolling

@interface MyScrollView : NSScrollView
@end

#import "MyScrollView.h"

@implementation MyScrollView

- (id)initWithFrame:(NSRect)frameRect
{
    self = [super initWithFrame:frameRect];
    if (self) {
        [self hideScrollers];
    }

    return self;
}

- (void)awakeFromNib
{
    [self hideScrollers];
}

- (void)hideScrollers
{
    // Hide the scrollers. You may want to do this if you're syncing the scrolling
    // this NSScrollView with another one.
    [self setHasHorizontalScroller:NO];
    [self setHasVerticalScroller:NO];
}

- (void)scrollWheel:(NSEvent *)theEvent
{
    // Do nothing: disable scrolling altogether
}

@end

I hope this helps.

titusmagnus
  • 2,014
  • 3
  • 23
  • 23
  • 2
    To be more precise : an NSTableView is not an NSScrollView, it is enclosed in one. So you'll have to change the enclosing NSScrollView to this custom class. – Guillaume Laurent Jul 31 '15 at 14:25
  • I downvoted because this causes strange and user-hostile behavior if this is the child of a working scroll view – Ky - Feb 23 '18 at 21:08
19

Here's the best solution in my opinion:

Swift 5

import Cocoa

@IBDesignable
@objc(BCLDisablableScrollView)
public class DisablableScrollView: NSScrollView {
    @IBInspectable
    @objc(enabled)
    public var isEnabled: Bool = true

    public override func scrollWheel(with event: NSEvent) {
        if isEnabled {
            super.scrollWheel(with: event)
        }
        else {
            nextResponder?.scrollWheel(with: event)
        }
    }
}


Simply replace any NSScrollView with DisablableScrollView (or BCLDisablableScrollView if you still use ObjC) and you're done. Simply set isEnabled in code or in IB and it will work as expected.

The main advantage that this has is for nested scroll views; disabling children without sending the event to the next responder will also effectively disable parents while the cursor is over the disabled child.

Here are all advantages of this approach listed out:

  • ✅ Disables scrolling
    • ✅ Does so programmatically, behaving normally by default
  • ✅ Does not interrupt scrolling a parent view
  • ✅ Interface Builder integration
  • ✅ Drop-in replacement for NSScrollView
  • ✅ Swift and Objective-C Compatible
Ky -
  • 30,724
  • 51
  • 192
  • 308
  • Given that the question is tagged objective-c, it's rather curious that your opinion is that a swift answer (which in my tiny swift knowledge, seems to be nothing more than a port of the existing objective c answers) is "best". Care you elaborate? – mah Feb 23 '18 at 20:56
  • @mah Well firstly the question was asked before Swift went public, so I doubt OP really specifically needs an ObjC answer Second, if you read the non-code text in my answer, you'll see the main advantage. In addition to that advantage, this includes IB integration. Also, in incorporates ideas from several other answers into one. – Ky - Feb 23 '18 at 21:04
  • 1
    Well, unless the op has moved his/her codebase from ObjC to swift, I wouldn't expect their needs to have changed ;) But the IB addition is cool (and one I didn't grasp prior to your comment -- like I mentioned, my swift knowledge is tiny.). Thanks! – mah Feb 23 '18 at 22:41
  • 1
    That's the beauty of Swift. Just drop this into Xcode and tell it to create a bridging header... and you're pretty much done. I started writing an Objective-C version (it's commented-out in the source if you wanna look), but stopped once I realized that I'd have to override constructors just to provide the default `true` value for `isEnabled`. Either way, this can be used in ObjC as well without modification! :D – Ky - Feb 23 '18 at 22:50
  • What is `BCLDisablableScrollView` ? – koen Sep 04 '21 at 11:55
  • @koen it's declared on line 4 of my example code – Ky - Sep 09 '21 at 20:47
  • @KyLeggiero ah yes, now I see. I thought it was from a 3rd party package. – koen Sep 11 '21 at 11:33
9

Thanks to @titusmagnus for the answer, but I made one modification so as not to break scrolling when when the "disabled" scrollView is nested within another scrollView: You can't scroll the outer scrollView while the cursor is within the bounds of the inner scrollView. If you do this...

- (void)scrollWheel:(NSEvent *)theEvent
{
    [self.nextResponder scrollWheel:theEvent];
    // Do nothing: disable scrolling altogether
}

...then the "disabled" scrollView will pass the scroll event up to the outer scrollView and its scrolling will not get stuck down inside its subviews.

proxpero
  • 2,206
  • 1
  • 16
  • 16
  • This answer provided what appears to me so far as the most practical way to work around NSTextView getting placed in a scrolling container by default. Thanks. – Anton Strogonoff Dec 01 '16 at 04:00
2

Works for me:

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

    if ([theEvent deltaY] != 0)
    {
        [[self nextResponder] scrollWheel:theEvent];
    }
}
Ivan Androsenko
  • 608
  • 6
  • 15
1

There is no simple direct way (meaning, there's no property like UITableView's scrollEnabled that you can set), but i found this answer helpful in the past.

One other thing you could try (not sure about this) is subclassing NSTableView and override -scrollWheel and -swipeWithEvent so they do nothing. Hope this helps

Community
  • 1
  • 1
jere
  • 4,306
  • 2
  • 27
  • 38