2015-03-23 20:28:42 +00:00
|
|
|
/**
|
|
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the BSD-style license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
*/
|
2015-02-20 04:10:52 +00:00
|
|
|
|
|
|
|
#import "RCTKeyCommands.h"
|
|
|
|
|
|
|
|
#import <UIKit/UIKit.h>
|
|
|
|
|
2015-06-22 15:08:35 +00:00
|
|
|
#import "RCTDefines.h"
|
2015-02-20 04:10:52 +00:00
|
|
|
#import "RCTUtils.h"
|
|
|
|
|
2015-06-22 15:08:35 +00:00
|
|
|
#if RCT_DEV
|
|
|
|
|
|
|
|
static BOOL RCTIsIOS8OrEarlier()
|
|
|
|
{
|
|
|
|
return [UIDevice currentDevice].systemVersion.floatValue < 9;
|
|
|
|
}
|
|
|
|
|
2015-06-19 15:08:14 +00:00
|
|
|
@interface RCTKeyCommand : NSObject <NSCopying>
|
|
|
|
|
|
|
|
@property (nonatomic, strong) UIKeyCommand *keyCommand;
|
|
|
|
@property (nonatomic, copy) void (^block)(UIKeyCommand *);
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation RCTKeyCommand
|
|
|
|
|
|
|
|
- (instancetype)initWithKeyCommand:(UIKeyCommand *)keyCommand
|
|
|
|
block:(void (^)(UIKeyCommand *))block
|
|
|
|
{
|
|
|
|
if ((self = [super init])) {
|
|
|
|
_keyCommand = keyCommand;
|
2015-06-22 15:08:35 +00:00
|
|
|
_block = block;
|
2015-06-19 15:08:14 +00:00
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2015-08-24 10:14:33 +00:00
|
|
|
RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
2015-06-19 15:08:14 +00:00
|
|
|
|
|
|
|
- (id)copyWithZone:(__unused NSZone *)zone
|
|
|
|
{
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSUInteger)hash
|
|
|
|
{
|
|
|
|
return _keyCommand.input.hash ^ _keyCommand.modifierFlags;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)isEqual:(RCTKeyCommand *)object
|
|
|
|
{
|
|
|
|
if (![object isKindOfClass:[RCTKeyCommand class]]) {
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
return [self matchesInput:object.keyCommand.input
|
|
|
|
flags:object.keyCommand.modifierFlags];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)matchesInput:(NSString *)input flags:(UIKeyModifierFlags)flags
|
|
|
|
{
|
|
|
|
return [_keyCommand.input isEqual:input] && _keyCommand.modifierFlags == flags;
|
|
|
|
}
|
|
|
|
|
2015-06-22 15:08:35 +00:00
|
|
|
- (NSString *)description
|
|
|
|
{
|
2017-09-25 17:23:02 +00:00
|
|
|
return [NSString stringWithFormat:@"<%@:%p input=\"%@\" flags=%lld hasBlock=%@>",
|
|
|
|
[self class], self, _keyCommand.input, (long long)_keyCommand.modifierFlags,
|
2015-06-22 15:08:35 +00:00
|
|
|
_block ? @"YES" : @"NO"];
|
|
|
|
}
|
|
|
|
|
2015-06-19 15:08:14 +00:00
|
|
|
@end
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
@interface RCTKeyCommands ()
|
|
|
|
|
2015-11-03 22:45:46 +00:00
|
|
|
@property (nonatomic, strong) NSMutableSet<RCTKeyCommand *> *commands;
|
2015-02-20 04:10:52 +00:00
|
|
|
|
|
|
|
@end
|
|
|
|
|
2015-06-22 15:08:35 +00:00
|
|
|
@implementation UIResponder (RCTKeyCommands)
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2016-06-01 10:29:11 +00:00
|
|
|
+ (UIResponder *)RCT_getFirstResponder:(UIResponder *)view
|
|
|
|
{
|
|
|
|
UIResponder *firstResponder = nil;
|
|
|
|
|
|
|
|
if (view.isFirstResponder) {
|
|
|
|
return view;
|
|
|
|
} else if ([view isKindOfClass:[UIViewController class]]) {
|
|
|
|
if ([(UIViewController *)view parentViewController]) {
|
|
|
|
firstResponder = [UIResponder RCT_getFirstResponder: [(UIViewController *)view parentViewController]];
|
|
|
|
}
|
|
|
|
return firstResponder ? firstResponder : [UIResponder RCT_getFirstResponder: [(UIViewController *)view view]];
|
|
|
|
} else if ([view isKindOfClass:[UIView class]]) {
|
|
|
|
for (UIView *subview in [(UIView *)view subviews]) {
|
|
|
|
firstResponder = [UIResponder RCT_getFirstResponder: subview];
|
|
|
|
if (firstResponder) {
|
|
|
|
return firstResponder;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return firstResponder;
|
|
|
|
}
|
|
|
|
|
2015-11-03 22:45:46 +00:00
|
|
|
- (NSArray<UIKeyCommand *> *)RCT_keyCommands
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
2015-11-03 22:45:46 +00:00
|
|
|
NSSet<RCTKeyCommand *> *commands = [RCTKeyCommands sharedInstance].commands;
|
2015-06-22 15:08:35 +00:00
|
|
|
return [[commands valueForKeyPath:@"keyCommand"] allObjects];
|
|
|
|
}
|
|
|
|
|
2016-06-01 10:29:11 +00:00
|
|
|
/**
|
|
|
|
* Single Press Key Command Response
|
|
|
|
* Command + KeyEvent (Command + R/D, etc.)
|
|
|
|
*/
|
2015-06-22 15:08:35 +00:00
|
|
|
- (void)RCT_handleKeyCommand:(UIKeyCommand *)key
|
|
|
|
{
|
|
|
|
// NOTE: throttle the key handler because on iOS 9 the handleKeyCommand:
|
|
|
|
// method gets called repeatedly if the command key is held down.
|
|
|
|
static NSTimeInterval lastCommand = 0;
|
|
|
|
if (RCTIsIOS8OrEarlier() || CACurrentMediaTime() - lastCommand > 0.5) {
|
|
|
|
for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance].commands) {
|
|
|
|
if ([command.keyCommand.input isEqualToString:key.input] &&
|
|
|
|
command.keyCommand.modifierFlags == key.modifierFlags) {
|
|
|
|
if (command.block) {
|
|
|
|
command.block(key);
|
|
|
|
lastCommand = CACurrentMediaTime();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
2016-06-01 10:29:11 +00:00
|
|
|
/**
|
|
|
|
* Double Press Key Command Response
|
|
|
|
* Double KeyEvent (Double R, etc.)
|
|
|
|
*/
|
|
|
|
- (void)RCT_handleDoublePressKeyCommand:(UIKeyCommand *)key
|
|
|
|
{
|
|
|
|
static BOOL firstPress = YES;
|
|
|
|
static NSTimeInterval lastCommand = 0;
|
|
|
|
static NSTimeInterval lastDoubleCommand = 0;
|
|
|
|
static NSString *lastInput = nil;
|
2016-07-12 12:51:56 +00:00
|
|
|
static UIKeyModifierFlags lastModifierFlags = 0;
|
2016-06-01 10:29:11 +00:00
|
|
|
|
|
|
|
if (firstPress) {
|
|
|
|
for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance].commands) {
|
|
|
|
if ([command.keyCommand.input isEqualToString:key.input] &&
|
|
|
|
command.keyCommand.modifierFlags == key.modifierFlags &&
|
|
|
|
command.block) {
|
|
|
|
|
|
|
|
firstPress = NO;
|
|
|
|
lastCommand = CACurrentMediaTime();
|
|
|
|
lastInput = key.input;
|
|
|
|
lastModifierFlags = key.modifierFlags;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Second keyevent within 0.2 second,
|
|
|
|
// with the same key as the first one.
|
|
|
|
if (CACurrentMediaTime() - lastCommand < 0.2 &&
|
|
|
|
lastInput == key.input &&
|
|
|
|
lastModifierFlags == key.modifierFlags) {
|
|
|
|
|
|
|
|
for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance].commands) {
|
|
|
|
if ([command.keyCommand.input isEqualToString:key.input] &&
|
|
|
|
command.keyCommand.modifierFlags == key.modifierFlags &&
|
|
|
|
command.block) {
|
|
|
|
|
|
|
|
// NOTE: throttle the key handler because on iOS 9 the handleKeyCommand:
|
|
|
|
// method gets called repeatedly if the command key is held down.
|
|
|
|
if (RCTIsIOS8OrEarlier() || CACurrentMediaTime() - lastDoubleCommand > 0.5) {
|
|
|
|
command.block(key);
|
|
|
|
lastDoubleCommand = CACurrentMediaTime();
|
|
|
|
}
|
|
|
|
firstPress = YES;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
lastCommand = CACurrentMediaTime();
|
|
|
|
lastInput = key.input;
|
|
|
|
lastModifierFlags = key.modifierFlags;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-22 15:08:35 +00:00
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation UIApplication (RCTKeyCommands)
|
|
|
|
|
|
|
|
// Required for iOS 8.x
|
2015-02-20 04:10:52 +00:00
|
|
|
- (BOOL)RCT_sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event
|
|
|
|
{
|
|
|
|
if (action == @selector(RCT_handleKeyCommand:)) {
|
2015-06-22 15:08:35 +00:00
|
|
|
[self RCT_handleKeyCommand:sender];
|
|
|
|
return YES;
|
2016-06-01 10:29:11 +00:00
|
|
|
} else if (action == @selector(RCT_handleDoublePressKeyCommand:)) {
|
|
|
|
[self RCT_handleDoublePressKeyCommand:sender];
|
|
|
|
return YES;
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
return [self RCT_sendAction:action to:target from:sender forEvent:event];
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation RCTKeyCommands
|
|
|
|
|
|
|
|
+ (void)initialize
|
2015-03-23 20:28:42 +00:00
|
|
|
{
|
2015-06-22 15:08:35 +00:00
|
|
|
if (RCTIsIOS8OrEarlier()) {
|
|
|
|
|
2016-06-01 10:29:11 +00:00
|
|
|
// swizzle UIApplication
|
2015-06-22 15:08:35 +00:00
|
|
|
RCTSwapInstanceMethods([UIApplication class],
|
|
|
|
@selector(keyCommands),
|
|
|
|
@selector(RCT_keyCommands));
|
|
|
|
|
|
|
|
RCTSwapInstanceMethods([UIApplication class],
|
|
|
|
@selector(sendAction:to:from:forEvent:),
|
|
|
|
@selector(RCT_sendAction:to:from:forEvent:));
|
|
|
|
} else {
|
|
|
|
|
2016-06-01 10:29:11 +00:00
|
|
|
// swizzle UIResponder
|
2015-06-22 15:08:35 +00:00
|
|
|
RCTSwapInstanceMethods([UIResponder class],
|
|
|
|
@selector(keyCommands),
|
|
|
|
@selector(RCT_keyCommands));
|
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
+ (instancetype)sharedInstance
|
|
|
|
{
|
|
|
|
static RCTKeyCommands *sharedInstance;
|
|
|
|
static dispatch_once_t onceToken;
|
|
|
|
dispatch_once(&onceToken, ^{
|
2015-08-17 14:35:34 +00:00
|
|
|
sharedInstance = [self new];
|
2015-02-20 04:10:52 +00:00
|
|
|
});
|
2015-03-23 20:28:42 +00:00
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
return sharedInstance;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (instancetype)init
|
|
|
|
{
|
|
|
|
if ((self = [super init])) {
|
2015-08-17 14:35:34 +00:00
|
|
|
_commands = [NSMutableSet new];
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)registerKeyCommandWithInput:(NSString *)input
|
|
|
|
modifierFlags:(UIKeyModifierFlags)flags
|
|
|
|
action:(void (^)(UIKeyCommand *))block
|
|
|
|
{
|
2016-06-06 14:57:55 +00:00
|
|
|
RCTAssertMainQueue();
|
2015-03-23 20:28:42 +00:00
|
|
|
|
2015-06-22 15:08:35 +00:00
|
|
|
if (input.length && flags && RCTIsIOS8OrEarlier()) {
|
2015-05-01 13:21:03 +00:00
|
|
|
|
|
|
|
// Workaround around the first cmd not working: http://openradar.appspot.com/19613391
|
|
|
|
// You can register just the cmd key and do nothing. This ensures that
|
2015-06-22 15:08:35 +00:00
|
|
|
// command-key modified commands will work first time. Fixed in iOS 9.
|
2015-05-01 13:21:03 +00:00
|
|
|
|
|
|
|
[self registerKeyCommandWithInput:@""
|
|
|
|
modifierFlags:flags
|
|
|
|
action:nil];
|
|
|
|
}
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input
|
|
|
|
modifierFlags:flags
|
|
|
|
action:@selector(RCT_handleKeyCommand:)];
|
2015-04-11 22:08:00 +00:00
|
|
|
|
2015-06-22 15:08:35 +00:00
|
|
|
RCTKeyCommand *keyCommand = [[RCTKeyCommand alloc] initWithKeyCommand:command block:block];
|
|
|
|
[_commands removeObject:keyCommand];
|
|
|
|
[_commands addObject:keyCommand];
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
2015-04-11 22:08:00 +00:00
|
|
|
- (void)unregisterKeyCommandWithInput:(NSString *)input
|
|
|
|
modifierFlags:(UIKeyModifierFlags)flags
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
2016-06-06 14:57:55 +00:00
|
|
|
RCTAssertMainQueue();
|
2015-03-23 20:28:42 +00:00
|
|
|
|
2015-06-19 15:08:14 +00:00
|
|
|
for (RCTKeyCommand *command in _commands.allObjects) {
|
|
|
|
if ([command matchesInput:input flags:flags]) {
|
|
|
|
[_commands removeObject:command];
|
2015-02-20 04:10:52 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)isKeyCommandRegisteredForInput:(NSString *)input
|
|
|
|
modifierFlags:(UIKeyModifierFlags)flags
|
|
|
|
{
|
2016-06-06 14:57:55 +00:00
|
|
|
RCTAssertMainQueue();
|
2015-03-23 20:28:42 +00:00
|
|
|
|
2015-06-19 15:08:14 +00:00
|
|
|
for (RCTKeyCommand *command in _commands) {
|
|
|
|
if ([command matchesInput:input flags:flags]) {
|
2015-02-20 04:10:52 +00:00
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
2016-06-01 10:29:11 +00:00
|
|
|
- (void)registerDoublePressKeyCommandWithInput:(NSString *)input
|
|
|
|
modifierFlags:(UIKeyModifierFlags)flags
|
|
|
|
action:(void (^)(UIKeyCommand *))block
|
|
|
|
{
|
2016-06-06 14:57:55 +00:00
|
|
|
RCTAssertMainQueue();
|
2016-06-01 10:29:11 +00:00
|
|
|
|
|
|
|
if (input.length && flags && RCTIsIOS8OrEarlier()) {
|
|
|
|
|
|
|
|
// Workaround around the first cmd not working: http://openradar.appspot.com/19613391
|
|
|
|
// You can register just the cmd key and do nothing. This ensures that
|
|
|
|
// command-key modified commands will work first time. Fixed in iOS 9.
|
|
|
|
|
|
|
|
[self registerDoublePressKeyCommandWithInput:@""
|
|
|
|
modifierFlags:flags
|
|
|
|
action:nil];
|
|
|
|
}
|
|
|
|
|
|
|
|
UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input
|
|
|
|
modifierFlags:flags
|
|
|
|
action:@selector(RCT_handleDoublePressKeyCommand:)];
|
|
|
|
|
|
|
|
RCTKeyCommand *keyCommand = [[RCTKeyCommand alloc] initWithKeyCommand:command block:block];
|
|
|
|
[_commands removeObject:keyCommand];
|
|
|
|
[_commands addObject:keyCommand];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)unregisterDoublePressKeyCommandWithInput:(NSString *)input
|
|
|
|
modifierFlags:(UIKeyModifierFlags)flags
|
|
|
|
{
|
2016-06-06 14:57:55 +00:00
|
|
|
RCTAssertMainQueue();
|
2016-06-01 10:29:11 +00:00
|
|
|
|
|
|
|
for (RCTKeyCommand *command in _commands.allObjects) {
|
|
|
|
if ([command matchesInput:input flags:flags]) {
|
|
|
|
[_commands removeObject:command];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)isDoublePressKeyCommandRegisteredForInput:(NSString *)input
|
|
|
|
modifierFlags:(UIKeyModifierFlags)flags
|
|
|
|
{
|
2016-06-06 14:57:55 +00:00
|
|
|
RCTAssertMainQueue();
|
2016-06-01 10:29:11 +00:00
|
|
|
|
|
|
|
for (RCTKeyCommand *command in _commands) {
|
|
|
|
if ([command matchesInput:input flags:flags]) {
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
@end
|
2015-06-22 15:08:35 +00:00
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
@implementation RCTKeyCommands
|
|
|
|
|
|
|
|
+ (instancetype)sharedInstance
|
|
|
|
{
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)registerKeyCommandWithInput:(NSString *)input
|
|
|
|
modifierFlags:(UIKeyModifierFlags)flags
|
|
|
|
action:(void (^)(UIKeyCommand *))block {}
|
|
|
|
|
|
|
|
- (void)unregisterKeyCommandWithInput:(NSString *)input
|
|
|
|
modifierFlags:(UIKeyModifierFlags)flags {}
|
|
|
|
|
|
|
|
- (BOOL)isKeyCommandRegisteredForInput:(NSString *)input
|
|
|
|
modifierFlags:(UIKeyModifierFlags)flags
|
|
|
|
{
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
2016-06-01 10:29:11 +00:00
|
|
|
- (void)registerDoublePressKeyCommandWithInput:(NSString *)input
|
|
|
|
modifierFlags:(UIKeyModifierFlags)flags
|
|
|
|
action:(void (^)(UIKeyCommand *))block {}
|
|
|
|
|
|
|
|
- (void)unregisterDoublePressKeyCommandWithInput:(NSString *)input
|
|
|
|
modifierFlags:(UIKeyModifierFlags)flags {}
|
|
|
|
|
|
|
|
- (BOOL)isDoublePressKeyCommandRegisteredForInput:(NSString *)input
|
|
|
|
modifierFlags:(UIKeyModifierFlags)flags
|
|
|
|
{
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
2015-06-22 15:08:35 +00:00
|
|
|
@end
|
|
|
|
|
|
|
|
#endif
|