【问题标题】:NSScrollView inside another NSScrollView另一个 NSScrollView 中的 NSScrollView
【发布时间】:2023-03-16 18:05:02
【问题描述】:

我有一个 NSScrollview 嵌套在另一个 NSScrollview 中。如何使内部视图仅处理水平滚动?垂直滚动应该移动外部视图。

目前我将scrollWheel:事件从内部视图传递到外部视图,但速度很慢。

【问题讨论】:

  • iPhone 还是 MacOS?我猜是 MacOS,因为你说的是​​ NSScrollView 而不是 UIScrollView。

标签: cocoa nsscrollview


【解决方案1】:

我也遇到了嵌套滚动视图的问题。内部滚动视图应水平滚动,外部应垂直滚动。

在处理来自魔术鼠标/触控板的滚动事件时,请务必为每个手势仅选择一个滚动视图,否则当您的手指移动不完全笔直时,您会看到奇怪的抽搐。您还应该确保用两根手指点击触控板会同时显示两个滚动条。

在处理来自强大鼠标或带有老式滚轮的鼠标的遗留滚动事件时,您必须为每个事件选择正确的滚动视图,因为事件中没有手势阶段信息。

这是我的内部滚动视图的子类,仅在 Mountain Lion 中测试过:

@interface PGEHorizontalScrollView : NSScrollView {
    BOOL currentScrollIsHorizontal;
}
@end

@implementation PGEHorizontalScrollView
-(void)scrollWheel:(NSEvent *)theEvent {
    /* Ensure that both scrollbars are flashed when the user taps trackpad with two fingers */
    if (theEvent.phase==NSEventPhaseMayBegin) {
        [super scrollWheel:theEvent];
        [[self nextResponder] scrollWheel:theEvent];
        return;
    }
    /* Check the scroll direction only at the beginning of a gesture for modern scrolling devices */
    /* Check every event for legacy scrolling devices */
    if (theEvent.phase == NSEventPhaseBegan || (theEvent.phase==NSEventPhaseNone && theEvent.momentumPhase==NSEventPhaseNone)) {
        currentScrollIsHorizontal = fabs(theEvent.scrollingDeltaX) > fabs(theEvent.scrollingDeltaY);
    }
    if ( currentScrollIsHorizontal ) {
        [super scrollWheel:theEvent];
    } else {
        [[self nextResponder] scrollWheel:theEvent];
    }
}
@end

我的实现并不总是正确地转发手势取消事件,但至少在 10.8 中这不会导致问题。

【讨论】:

  • 这对我来说主要是在 10.10 上。我需要添加+(BOOL)isCompatibleWithResponsiveScrolling { return YES; },因为NSScrollView 检测到-scrollWheel: 的覆盖,并且我认为无响应滚动存在错误。
  • 我怀疑上面的 scrollWheel 方法没有按照你的想法做。在“开始”阶段,您永远无法达到 isHorizo​​ntal 检查。此外,在那个阶段,滚动增量总是得到 0,0。只需 always 调用 super 然后传递给下一个响应者,事情实际上就可以正常工作。请注意,如果您在这些滚动视图中的任何位置使用 NSTrackingAreas,我认为这会导致问题。
  • @Giles 你确定你没有混淆NSEventPhaseMayBeginNSEventPhaseBegan 阶段吗? “mayBegin”阶段发生在用户将两根手指放在触控板上时。 “开始”阶段是当用户开始移动手指时,scrollingDeltaXscrollingDeltaY 中的至少一个在该点不为零。对于使用传统滚轮的鼠标来说,总是调用超级响应者和下一个响应者可以正常工作,但据我所知,它会导致动量滚动(触摸板、魔术鼠标)出现问题。
  • 不管怎样,我从 2012 年开始就一直在使用这种方法,并且从未报告过任何问题。我在 macOS 10.8 - 10.14 上使用过它,包括触控板和第 3 方鼠标。如果有问题,我很想知道您使用的是什么版本的 macOS,以及您使用的输入设备。
  • @JakobEgger 我真诚的道歉。我犯了你所描述的错误——还有更多!我希望我可以对自己的评论投反对票!感谢您提供更详细的回复。我使用的是“deltaX”而不是“scrollingDeltaX”,它们在滚动的生命周期中给出了非常不同的值。它还修复了我在使用 NStrackingArea 时遇到的所有让我发疯的错误——这是一个非常微妙的问题。我正在翻译成 Swift,这是我糟糕的借口......
【解决方案2】:

这是我的 NSScrollView 子类,可以满足您的要求。由于它只是传递事件,它并不关心响应者链的上游,它应该像它不是子类(或至少关闭)一样高效

h 文件

#import <Cocoa/Cocoa.h>

@interface HorizontalScrollView : NSScrollView

@end

和m

@implementation HorizontalScrollView

- (void)scrollWheel:(NSEvent *)theEvent {
    NSLog(@"%@", theEvent);
    if(theEvent.deltaX !=0)
        [super scrollWheel:theEvent];
    else
        [[self nextResponder] scrollWheel:theEvent];

}
@end

【讨论】:

  • 这种方法会导致滚动视图中的 NSTrackingAreas 出现微妙的问题 - 例如,这包括文本视图链接上的内置光标效果。请参阅 Jakob 的回答和下面的 cmets。
【解决方案3】:

斯威夫特 4。

Answer 是 Jakob 上述出色答案的翻译,并添加了方向标志。

正如 cmets 在他的回答中所提到的,如果做得不好,可能会出现微妙的并发症。当简单地将所有 scrollWheel() 调用转发给下一个响应者时,我看到 NSTrackingAreas 未正确更新以及 NSTextView 视图上的工具提示和光标样式未对齐的问题。

class ECGuidedScrollView: NSScrollView
{
    enum ScrollDirection {
        case vertical
        case horizontal
    }

    private var currentScrollMatches: Bool = false
    private var scrollDirection: ScrollDirection

    init(withDirection direction: ScrollDirection) {
        scrollDirection = direction
        super.init(frame: NSRect.zero)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func scrollWheel(with event: NSEvent)
    {        
        // Ensure that both scrollbars are flashed when the user taps trackpad with two fingers
        if event.phase == .mayBegin {
            super.scrollWheel(with: event)
            nextResponder?.scrollWheel(with: event)
            return
        }

        /*
         Check the scroll direction only at the beginning of a gesture for modern scrolling devices
         Check every event for legacy scrolling devices
         */

        if event.phase == .began || (event.phase == [] && event.momentumPhase == [])
        {
            switch scrollDirection {
            case .vertical:
                currentScrollMatches = fabs(event.scrollingDeltaY) > fabs(event.scrollingDeltaX)
            case .horizontal:
                currentScrollMatches = fabs(event.scrollingDeltaX) > fabs(event.scrollingDeltaY)
            }
        }

        if currentScrollMatches {
            super.scrollWheel(with: event)
        } else {
            self.nextResponder?.scrollWheel(with: event)
        }
    }
}

【讨论】:

  • 文档说 NSEvent.Phase 是一个选项集,所以你应该可以使用event.phase == [] 而不是event.phase.rawValue == 0。此外,如果您将 scrollDirection 设为 public 并提供默认值,则不需要初始化程序,并且在 XIB 中更容易使用。
  • 谢谢雅各布。我已经更正了 OptionSet 检查。我不是 IB 用户,所以我宁愿强制执行清晰的代码,我将您建议的更改留给其他人......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-19
  • 1970-01-01
相关资源
最近更新 更多