mirror of
https://github.com/status-im/react-native.git
synced 2025-01-27 01:40:08 +00:00
61046c3195
Summary: This is a followup for "Add Shortcut "Double R" to Reload JS in iOS". Please see the previous two revisions:[[ D3371536 | D3371536 ]], [[ D3343907 | D3343907 ]] In previous revisions, we only tested with the iOS UIExplorer app, without testing in the iOS Catalyst app, where the key shortcuts we added are always invoked in TextInput components. It's due to a bug with the `UIApplicationDelegate`. Just fix this bug in this revision and successfully tested in the Catalyst app. Reviewed By: mkonicek Differential Revision: D3391045 fbshipit-source-id: 8b76fbfe7592218b02dd22502d25eebbc59f3cbc
410 lines
12 KiB
Objective-C
410 lines
12 KiB
Objective-C
/**
|
|
* 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.
|
|
*/
|
|
|
|
#import "RCTKeyCommands.h"
|
|
|
|
#import <UIKit/UIKit.h>
|
|
|
|
#import "RCTDefines.h"
|
|
#import "RCTUtils.h"
|
|
|
|
#if RCT_DEV
|
|
|
|
static BOOL RCTIsIOS8OrEarlier()
|
|
{
|
|
return [UIDevice currentDevice].systemVersion.floatValue < 9;
|
|
}
|
|
|
|
@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;
|
|
_block = block;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
|
|
|
- (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;
|
|
}
|
|
|
|
- (NSString *)description
|
|
{
|
|
return [NSString stringWithFormat:@"<%@:%p input=\"%@\" flags=%zd hasBlock=%@>",
|
|
[self class], self, _keyCommand.input, _keyCommand.modifierFlags,
|
|
_block ? @"YES" : @"NO"];
|
|
}
|
|
|
|
@end
|
|
|
|
@interface RCTKeyCommands ()
|
|
|
|
@property (nonatomic, strong) NSMutableSet<RCTKeyCommand *> *commands;
|
|
|
|
@end
|
|
|
|
@implementation UIResponder (RCTKeyCommands)
|
|
|
|
+ (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;
|
|
}
|
|
|
|
- (NSArray<UIKeyCommand *> *)RCT_keyCommands
|
|
{
|
|
/*
|
|
* If current first responder is UITextView or UITextField, disable the shortcut key commands.
|
|
* For example, double-pressing a key should type two characters into the text view,
|
|
* not invoke a predefined keyboard shortcut.
|
|
*/
|
|
UIResponder *firstResponder = [UIResponder RCT_getFirstResponder: self];
|
|
if ([firstResponder isKindOfClass:[UITextView class]] ||
|
|
[firstResponder isKindOfClass:[UITextField class]] ||
|
|
[firstResponder conformsToProtocol:@protocol(UITextViewDelegate)] ||
|
|
[self conformsToProtocol:@protocol(UIApplicationDelegate)]) {
|
|
return nil;
|
|
}
|
|
|
|
NSSet<RCTKeyCommand *> *commands = [RCTKeyCommands sharedInstance].commands;
|
|
return [[commands valueForKeyPath:@"keyCommand"] allObjects];
|
|
}
|
|
|
|
/**
|
|
* Single Press Key Command Response
|
|
* Command + KeyEvent (Command + R/D, etc.)
|
|
*/
|
|
- (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();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
static UIKeyModifierFlags lastModifierFlags = nil;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation UIApplication (RCTKeyCommands)
|
|
|
|
// Required for iOS 8.x
|
|
- (BOOL)RCT_sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event
|
|
{
|
|
if (action == @selector(RCT_handleKeyCommand:)) {
|
|
[self RCT_handleKeyCommand:sender];
|
|
return YES;
|
|
} else if (action == @selector(RCT_handleDoublePressKeyCommand:)) {
|
|
[self RCT_handleDoublePressKeyCommand:sender];
|
|
return YES;
|
|
}
|
|
return [self RCT_sendAction:action to:target from:sender forEvent:event];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation RCTKeyCommands
|
|
|
|
+ (void)initialize
|
|
{
|
|
if (RCTIsIOS8OrEarlier()) {
|
|
|
|
// swizzle UIApplication
|
|
RCTSwapInstanceMethods([UIApplication class],
|
|
@selector(keyCommands),
|
|
@selector(RCT_keyCommands));
|
|
|
|
RCTSwapInstanceMethods([UIApplication class],
|
|
@selector(sendAction:to:from:forEvent:),
|
|
@selector(RCT_sendAction:to:from:forEvent:));
|
|
} else {
|
|
|
|
// swizzle UIResponder
|
|
RCTSwapInstanceMethods([UIResponder class],
|
|
@selector(keyCommands),
|
|
@selector(RCT_keyCommands));
|
|
}
|
|
}
|
|
|
|
+ (instancetype)sharedInstance
|
|
{
|
|
static RCTKeyCommands *sharedInstance;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
sharedInstance = [self new];
|
|
});
|
|
|
|
return sharedInstance;
|
|
}
|
|
|
|
- (instancetype)init
|
|
{
|
|
if ((self = [super init])) {
|
|
_commands = [NSMutableSet new];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)registerKeyCommandWithInput:(NSString *)input
|
|
modifierFlags:(UIKeyModifierFlags)flags
|
|
action:(void (^)(UIKeyCommand *))block
|
|
{
|
|
RCTAssertMainQueue();
|
|
|
|
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 registerKeyCommandWithInput:@""
|
|
modifierFlags:flags
|
|
action:nil];
|
|
}
|
|
|
|
UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input
|
|
modifierFlags:flags
|
|
action:@selector(RCT_handleKeyCommand:)];
|
|
|
|
RCTKeyCommand *keyCommand = [[RCTKeyCommand alloc] initWithKeyCommand:command block:block];
|
|
[_commands removeObject:keyCommand];
|
|
[_commands addObject:keyCommand];
|
|
}
|
|
|
|
- (void)unregisterKeyCommandWithInput:(NSString *)input
|
|
modifierFlags:(UIKeyModifierFlags)flags
|
|
{
|
|
RCTAssertMainQueue();
|
|
|
|
for (RCTKeyCommand *command in _commands.allObjects) {
|
|
if ([command matchesInput:input flags:flags]) {
|
|
[_commands removeObject:command];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (BOOL)isKeyCommandRegisteredForInput:(NSString *)input
|
|
modifierFlags:(UIKeyModifierFlags)flags
|
|
{
|
|
RCTAssertMainQueue();
|
|
|
|
for (RCTKeyCommand *command in _commands) {
|
|
if ([command matchesInput:input flags:flags]) {
|
|
return YES;
|
|
}
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (void)registerDoublePressKeyCommandWithInput:(NSString *)input
|
|
modifierFlags:(UIKeyModifierFlags)flags
|
|
action:(void (^)(UIKeyCommand *))block
|
|
{
|
|
RCTAssertMainQueue();
|
|
|
|
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
|
|
{
|
|
RCTAssertMainQueue();
|
|
|
|
for (RCTKeyCommand *command in _commands.allObjects) {
|
|
if ([command matchesInput:input flags:flags]) {
|
|
[_commands removeObject:command];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (BOOL)isDoublePressKeyCommandRegisteredForInput:(NSString *)input
|
|
modifierFlags:(UIKeyModifierFlags)flags
|
|
{
|
|
RCTAssertMainQueue();
|
|
|
|
for (RCTKeyCommand *command in _commands) {
|
|
if ([command matchesInput:input flags:flags]) {
|
|
return YES;
|
|
}
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
@end
|
|
|
|
#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;
|
|
}
|
|
|
|
- (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;
|
|
}
|
|
|
|
@end
|
|
|
|
#endif
|