8
9

More than 5 years have passed since last update.

CALayerでNSResponderのイベントをキャプチャする

Posted at

CALayerはNSViewに比べてとても便利でUIViewに近い機能を持っていますが、イベントハンドラがありません。そのため、いろいろなイベントは自由に取得できませんが、CALayerが元をたどれば何かのNSViewのlayerにaddSublayer:されるという前提があれば、イベントを自由にキャプチャできます。

Cocoaでは(というか、普通のウィンドウシステムなら?)イベントハンドリングはレスポンダチェーンを辿って最終的にrootのNSwindowオブジェクトにバブルアップします。NSResponderにはsetNextResponder:というメソッドがあるので、これを使うと普通にイベントチェーンを全く無関係のオブジェクトからキャプチャできます。

そこでこんなクラスを作りました。

ResponderProxy

typedef void (^ResponderHandler)(NSEvent *e);

@interface ResponderProxy : NSResponder

- (instancetype)initWithHandler:(ResponderHandler)hander;
- (instancetype)initWithDelegate:(id)delegate selector:(SEL)selector;

@property (nonatomic, weak) id delegate;
@property (nonatomic) SEL selector;
@property (nonatomic, copy) ResponderHandler eventandler;

@end

@implementation ResponderProxy

- (instancetype)initWithHandler:(ResponderHandler)hander
{
    self = [self init];
    self.eventandler = hander;
    return self ?: nil;
}

- (instancetype)initWithDelegate:(id)delegate selector:(SEL)selector
{
    self = [self init];
    self.delegate = delegate;
    self.selector = selector;
    return self ?: nil;
}

- (void)mouseDown:(NSEvent *)theEvent
{
    if (self.eventandler) {
        self.eventandler(theEvent);
    }else if (self.delegate && self.selector) {
        if ([self.delegate respondsToSelector:self.selector]) {
            objc_msgSend(self.delegate, self.selector, theEvent);
        }
    }
}

@end

@interface SomeLayer : CALayer

// レイヤーが所属するrootのNSView
@property (nonatomic, weak) NSView *rootView;

@end

@implementation SomeLayer
{
  ResponderProxy *_responderProxy;
}

- (instancetype)initWithRootView:(NSView*)rootView
{
    self = [self init];
    // レスポンダプロクシを挟んでこのクラスでキャプチャする
    _responderProxy = [[ResponderProxy alloc] initWithDelegate:self selector:@selector(handleNSEvent:)];
    NSResponder *next = rootView.nextResponder;
    [rootView setNextResponder:_responderProxy];
    [_responderProxy setNextResponder:next];
    _rootView = rootView;
    return self ?: nil;
}

- (void)handleNSEvent:(NSEvent*)event
{
    NSLog(@"%@",event);
}

これでrootViewの上でクリックするとログが出力されます。用途に合わせてハンドラを変えると便利だと思います。
UIResponderにはsetNextResponder:がありませんが、UIGestureRecognizerを使えば同じことが出来ると思います。

8
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
9