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を使えば同じことが出来ると思います。