How to pass scroll events to parent NSScrollView
Asked Answered
F

5

9

I need fixed-size NSTextViews inside a larger scrolling window. IB requires that the textviews be inside their own NSScrollViews, even though their min/max sizes are fixed so that they won’t actually scroll. When trackpad gestures are made within the textview frames (regardless of whether they have focus), they are captured by the textviews’ scrollviews, so nothing happens.

How do I tell the textviews’ scrollviews to pass scroll events up to the window’s main scrollview? (Or perhaps I should be asking how I tell the window’s main scrollview to handle these events itself and not pass them on to its child scrollviews.)

The IB structure is like this:

  • window
    • window’s content view
      • big scrollview for window (desired target for scroll events)
        • box
          • swappable content view in separate xib
            • scrollview for textview
              • textview
And, yes, the window does scroll correctly when the textviews do not have focus.
Footstone answered 9/6, 2011 at 17:40 Comment(0)
F
3

This is really embarrassing. After weeks of putting it off, I made a first attempt to get a subclassed NSScrollView to behave passively — and it turned out to be a no brainer.

Here’s the subclass:

h file:

#import <Cocoa/Cocoa.h>

@interface ScrollViewPassive : NSScrollView {

// This property is assigned a ref to windowController’s main scrollview.
NSScrollView *svActive; 

}

@property (nonatomic, retain) NSScrollView *svActive;

@end

m file:

#import "ScrollViewPassive.h"

@implementation ScrollViewPassive

@synthesize svActive;

// Pass any gesture scrolling up to the main, active scrollview.
- (void)scrollWheel:(NSEvent *)event {
    [svActive scrollWheel:event];
}

@end

There’s no need to make outlets for these passive scrollviews; I give them their refs to the main scrollview right after their xibs are assigned as content to the NSBox:

    [self.boxDisplayingTextViews setContentView:self.subviewCtllr1.view];
    // A textview's superview's superview is its scrollview:    
    ((ScrollViewPassive *)[[self.subviewCtllr1.textview1 superview] superview]).svActive = self.scrollviewMain;

That’s it. Works like a charm.

Footstone answered 25/6, 2011 at 17:39 Comment(0)
B
12

You needn't create a outlet "svActive" to track your super scrollview. Just write this sentence in scrollWheel event:

[[self nextResponder] scrollWheel:event];

this will pass the event to next responder in the responder chain.

Briareus answered 17/10, 2011 at 2:53 Comment(1)
Nice tip. Yes, that does work, but at least in this project, the next responder is not svActive, so a series of calls must be made through the chain until svActive is reached. Possibly the nextResponder calls are so cheap that this doesn't matter, but sending the event explicitly to svActive makes the code clearer, so I'm leaving it as is.Footstone
C
7

IB does not require you have a text view inside a NSScrollView; this is just the default, because most of the time you'll want your view to scroll. Select the NSTextView and choose Layout > Unembed Objects. Note that after this point, you can no longer move or resize your view in IB. This seems to be a bug.

Here's an example of how to put two NSTextViews in a single NSScrollView.

Add two text views next to each other; put some text in them so you can see what's happening.

Select the views; choose Layout > Embed Objects In > Scroll View. This puts them in a generic NSView inside a NSScrollView.

Select the text views; choose Layout > Unembed Objects.

Turn off the springs and struts (autosizing) for each text view, so they don't resize when you shrink the scroll view.

Take note of the height of the document view (here it's 175).

Make the scroll view smaller. This also resizes the document view (NSView).

Restore the document view to its original size (I set the height back to 175).

Done! Scrolling works as you'd expect.

Colettecoleus answered 10/6, 2011 at 2:40 Comment(7)
That didn't work in IB3, but I just tried it in Xcode 4 and it might be working. You can't seem to adjust its location once you unembed it. Seems like it may just be buggy now.Member
I just tried it in IB 3.2.6 and it has the same issue with not allowing its location to be changed, but it works. I am realizing now, though, that my knowledge of NSScrollView is woefully inadequate.Colettecoleus
Ah; I see the difference in 3.2.6. If you select the textview in the document window, then "Unembed Objects" is grayed out. If you select it by clicking on the textview itself, then it "works" (sort of). IB really doesn't like it in any case. Rearranging the view at runtime has been the answer for a long time. lists.apple.com/archives/cocoa-dev/2001/May/msg00085.htmlMember
I did get this to work entirely in IB (but, as you said, IB doesn't like it). I'll update my answer with a longer explanation.Colettecoleus
@Nicholas Riley: Beautiful example. I’ll keep it for reference. Unfortunately, I can’t use it in this project because the textview has to be in a separate nib from that of the window’s scrollview.Footstone
Well, you could still do the same thing in the separate xib; I was just doing everything in a single xib to keep things simple.Colettecoleus
Xcode 4.5.2 goes as far as prohibiting NSTextView from being un-embedded from its NSScrollView.Smalls
F
3

This is really embarrassing. After weeks of putting it off, I made a first attempt to get a subclassed NSScrollView to behave passively — and it turned out to be a no brainer.

Here’s the subclass:

h file:

#import <Cocoa/Cocoa.h>

@interface ScrollViewPassive : NSScrollView {

// This property is assigned a ref to windowController’s main scrollview.
NSScrollView *svActive; 

}

@property (nonatomic, retain) NSScrollView *svActive;

@end

m file:

#import "ScrollViewPassive.h"

@implementation ScrollViewPassive

@synthesize svActive;

// Pass any gesture scrolling up to the main, active scrollview.
- (void)scrollWheel:(NSEvent *)event {
    [svActive scrollWheel:event];
}

@end

There’s no need to make outlets for these passive scrollviews; I give them their refs to the main scrollview right after their xibs are assigned as content to the NSBox:

    [self.boxDisplayingTextViews setContentView:self.subviewCtllr1.view];
    // A textview's superview's superview is its scrollview:    
    ((ScrollViewPassive *)[[self.subviewCtllr1.textview1 superview] superview]).svActive = self.scrollviewMain;

That’s it. Works like a charm.

Footstone answered 25/6, 2011 at 17:39 Comment(0)
M
1

I find that IB3 and Xcode4 both fight you if you try to do this directly, but you can do it indirectly. First, drag the textview out of the scrollview and delete the scrollview. You'll wind up with an orphaned textview. I don't know any way to get IB to allow you to put this into your window, but it'll be in your NIB. Now, attach an IBOutlet to it, and at runtime do a addSubview: and adjust its frame to move it into whatever scrollview you wanted it to be in.

In my experience, NIBs are a good thing, but every complex NIB I've ever worked with needed some final rearranging in code.

Member answered 10/6, 2011 at 2:39 Comment(1)
Thanks for your advice. Here, though, when I try addSubview/setFrame on the orphaned textview, nothing shows up. Upon debugging, I see the frame size remains 0,0. I tried it with an orphaned textfield outlet to make sure my code was correct and that worked fine. (But even if I succeed in setting the textview frames, moving them directly into the window’s scrollview will make serious difficulties with multiple box disclosure triangles and layer animations involving snapshots of the box’s contents.) The user might have to stick with the scrollbar…Footstone
T
1

Based on @phaibin's answer, here's how you'd do it in Swift 4.2.

First, subclass NSScrollView and override scrollWheel:

class ScrollThingy: NSScrollView{
  override func scrollWheel(with event: NSEvent) {
    self.nextResponder?.scrollWheel(with: event)
  }
}

Then place the ScrollThingy class (or whatever you name it) on the NSScrollView that is wrapped around your NSTextView. The parent NSScrollView will get the scroll event thereafter.

Toneytong answered 5/2, 2019 at 21:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.