NSScrollView внутри другого NSScrollView

У меня есть NSScrollview, вложенный в другой NSScrollview. Как сделать так, чтобы внутренний вид обрабатывал только горизонтальную прокрутку? Вертикальная прокрутка должна перемещать внешний вид.

В настоящее время я передаю событие scrollWheel: во внешний вид из внутреннего вида, но это очень медленно.


person NeXT5tep    schedule 24.12.2011    source источник
comment
Айфон или MacOS? Я предполагаю, что MacOS, так как вы говорите NSScrollView, а не UIScrollView.   -  person Michael Dautermann    schedule 24.12.2011


Ответы (3)


У меня также была проблема с вложенными представлениями прокрутки. Внутреннее представление прокрутки должно прокручиваться горизонтально, а внешнее — вертикально.

При обработке событий прокрутки с помощью волшебной мыши/трекпада важно выбирать только одно из представлений прокрутки для каждого жеста, иначе вы увидите странные рывки, когда ваши пальцы двигаются не идеально прямо. Вы также должны убедиться, что при касании трекпада двумя пальцами отображаются оба скроллера.

При обработке устаревших событий прокрутки от могучей мыши или мышей со старомодными колесиками прокрутки вы должны выбрать правильное представление прокрутки для каждого события, потому что в событиях нет информации о фазе жеста.

Это мой подкласс для просмотра внутренней прокрутки, проверенный только в 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 это не вызывает проблем.

person Jakob Egger    schedule 29.11.2012
comment
Это работало для меня на 10.10, в основном. Мне нужно было добавить +(BOOL)isCompatibleWithResponsiveScrolling { return YES; }, потому что NSScrollView обнаруживает переопределение -scrollWheel:, и я думаю, что в прокрутке без ответа есть ошибка. - person joerick; 20.05.2015
comment
Я подозреваю, что приведенный выше метод scrollWheel не делает то, что вы думаете. На этапе «начало» вы никогда не сможете добраться до проверки isHorizontal. Кроме того, на этом этапе я всегда получаю 0,0 для дельт прокрутки. На самом деле все работает нормально, просто всегда вызывая super, а затем все равно переходя к следующему ответчику. Имейте в виду, что я думаю, что это вызывает проблемы с NSTrackingAreas, если вы используете их где-либо внутри этих представлений прокрутки. - person Giles; 18.12.2018
comment
@Giles Вы уверены, что не путаете фазы NSEventPhaseMayBegin и NSEventPhaseBegan? Фаза mayBegin возникает, когда пользователь кладет два пальца на трекпад. Начальная фаза — это когда пользователь начинает двигать пальцами, и хотя бы одно из scrollingDeltaX или scrollingDeltaY в этот момент не равно нулю. Всегда вызывать супер и следующего ответчика нормально работает для мышей с традиционными колесиками прокрутки, но, насколько я помню, это вызывает проблемы с прокруткой по импульсу (сенсорная панель, волшебная мышь). - person Jakob Egger; 18.12.2018
comment
Во всяком случае, я использую этот метод с 2012 года, и никаких проблем с ним не было. Я использовал его на macOS 10.8–10.14, как с трекпадом, так и с мышами сторонних производителей. Если есть проблема с этим, я хотел бы услышать, какую версию macOS и какое устройство ввода вы используете. - person Jakob Egger; 18.12.2018
comment
@JakobEgger Мои искренние извинения. Я совершил именно ту ошибку, которую вы описали, и даже больше! Хотел бы я проголосовать за свой собственный комментарий! Спасибо, что ответили более подробно. Я использовал deltaX, а не scrollingDeltaX, которые дают очень разные значения в течение жизненного цикла прокрутки. Он также исправил все ошибки, которые я получал с NStrackingArea, которые сводили меня с ума — такая тонкая проблема. Я переводил на Swift, что является моим ужасным оправданием... - person Giles; 19.12.2018
comment
@ Джайлс, я вижу, вы опубликовали быстрый перевод - очень полезно! - person Jakob Egger; 19.12.2018

Это мой подкласс NSScrollView, который делает то, что вы просите. Поскольку он просто передает события, которые ему не нужны, вверх по цепочке респондентов, он должен быть таким же производительным, как если бы он не был подклассом (или, по крайней мере, близким)

ч файл

#import <Cocoa/Cocoa.h>

@interface HorizontalScrollView : NSScrollView

@end

И м

@implementation HorizontalScrollView

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

}
@end
person user961889    schedule 19.07.2012
comment
Этот подход вызовет тонкие проблемы с NSTrackingAreas в ваших представлениях прокрутки — это включает, например, встроенные эффекты курсора для ссылок текстового представления. См. ответ Якоба и комментарии ниже. - person Giles; 19.12.2018

Свифт 4.

Ответ представляет собой перевод превосходного ответа Якоба выше с добавлением флага направления.

Как упоминалось в комментариях к его ответу, могут возникнуть тонкие осложнения, если это не будет сделано правильно. Когда я просто перенаправлял все вызовы 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)
        }
    }
}
person Giles    schedule 19.12.2018
comment
В документах говорится, что NSEvent.Phase — это OptionSet, поэтому вы должны иметь возможность использовать event.phase == [] вместо event.phase.rawValue == 0. Кроме того, если бы вы сделали scrollDirection общедоступным и предоставили значение по умолчанию, вам не понадобился бы инициализатор, и его было бы проще использовать в XIB. - person Jakob Egger; 19.12.2018
comment
Спасибо Якоб. Я исправил проверку OptionSet. Я не пользователь IB, поэтому я бы предпочел внести ясность в код, я оставлю ваши предложенные изменения для других... - person Giles; 20.12.2018