RCTKeyCommands.m 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. /*
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. #import "RCTKeyCommands.h"
  8. #import <UIKit/UIKit.h>
  9. #import "RCTDefines.h"
  10. #import "RCTUtils.h"
  11. #if RCT_DEV
  12. @interface RCTKeyCommand : NSObject <NSCopying>
  13. @property (nonatomic, strong) UIKeyCommand *keyCommand;
  14. @property (nonatomic, copy) void (^block)(UIKeyCommand *);
  15. @end
  16. @implementation RCTKeyCommand
  17. - (instancetype)initWithKeyCommand:(UIKeyCommand *)keyCommand block:(void (^)(UIKeyCommand *))block
  18. {
  19. if ((self = [super init])) {
  20. _keyCommand = keyCommand;
  21. _block = block;
  22. }
  23. return self;
  24. }
  25. RCT_NOT_IMPLEMENTED(-(instancetype)init)
  26. - (id)copyWithZone:(__unused NSZone *)zone
  27. {
  28. return self;
  29. }
  30. - (NSUInteger)hash
  31. {
  32. return _keyCommand.input.hash ^ _keyCommand.modifierFlags;
  33. }
  34. - (BOOL)isEqual:(RCTKeyCommand *)object
  35. {
  36. if (![object isKindOfClass:[RCTKeyCommand class]]) {
  37. return NO;
  38. }
  39. return [self matchesInput:object.keyCommand.input flags:object.keyCommand.modifierFlags];
  40. }
  41. - (BOOL)matchesInput:(NSString *)input flags:(UIKeyModifierFlags)flags
  42. {
  43. return [_keyCommand.input isEqual:input] && _keyCommand.modifierFlags == flags;
  44. }
  45. - (NSString *)description
  46. {
  47. return [NSString stringWithFormat:@"<%@:%p input=\"%@\" flags=%lld hasBlock=%@>",
  48. [self class],
  49. self,
  50. _keyCommand.input,
  51. (long long)_keyCommand.modifierFlags,
  52. _block ? @"YES" : @"NO"];
  53. }
  54. @end
  55. @interface RCTKeyCommands ()
  56. @property (nonatomic, strong) NSMutableSet<RCTKeyCommand *> *commands;
  57. @end
  58. @implementation UIResponder (RCTKeyCommands)
  59. + (UIResponder *)RCT_getFirstResponder:(UIResponder *)view
  60. {
  61. UIResponder *firstResponder = nil;
  62. if (view.isFirstResponder) {
  63. return view;
  64. } else if ([view isKindOfClass:[UIViewController class]]) {
  65. if ([(UIViewController *)view parentViewController]) {
  66. firstResponder = [UIResponder RCT_getFirstResponder:[(UIViewController *)view parentViewController]];
  67. }
  68. return firstResponder ? firstResponder : [UIResponder RCT_getFirstResponder:[(UIViewController *)view view]];
  69. } else if ([view isKindOfClass:[UIView class]]) {
  70. for (UIView *subview in [(UIView *)view subviews]) {
  71. firstResponder = [UIResponder RCT_getFirstResponder:subview];
  72. if (firstResponder) {
  73. return firstResponder;
  74. }
  75. }
  76. }
  77. return firstResponder;
  78. }
  79. - (NSArray<UIKeyCommand *> *)RCT_keyCommands
  80. {
  81. NSSet<RCTKeyCommand *> *commands = [RCTKeyCommands sharedInstance].commands;
  82. return [[commands valueForKeyPath:@"keyCommand"] allObjects];
  83. }
  84. /**
  85. * Single Press Key Command Response
  86. * Command + KeyEvent (Command + R/D, etc.)
  87. */
  88. - (void)RCT_handleKeyCommand:(UIKeyCommand *)key
  89. {
  90. // NOTE: throttle the key handler because on iOS 9 the handleKeyCommand:
  91. // method gets called repeatedly if the command key is held down.
  92. static NSTimeInterval lastCommand = 0;
  93. if (CACurrentMediaTime() - lastCommand > 0.5) {
  94. for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance].commands) {
  95. if ([command.keyCommand.input isEqualToString:key.input] &&
  96. command.keyCommand.modifierFlags == key.modifierFlags) {
  97. if (command.block) {
  98. command.block(key);
  99. lastCommand = CACurrentMediaTime();
  100. }
  101. }
  102. }
  103. }
  104. }
  105. /**
  106. * Double Press Key Command Response
  107. * Double KeyEvent (Double R, etc.)
  108. */
  109. - (void)RCT_handleDoublePressKeyCommand:(UIKeyCommand *)key
  110. {
  111. static BOOL firstPress = YES;
  112. static NSTimeInterval lastCommand = 0;
  113. static NSTimeInterval lastDoubleCommand = 0;
  114. static NSString *lastInput = nil;
  115. static UIKeyModifierFlags lastModifierFlags = 0;
  116. if (firstPress) {
  117. for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance].commands) {
  118. if ([command.keyCommand.input isEqualToString:key.input] &&
  119. command.keyCommand.modifierFlags == key.modifierFlags && command.block) {
  120. firstPress = NO;
  121. lastCommand = CACurrentMediaTime();
  122. lastInput = key.input;
  123. lastModifierFlags = key.modifierFlags;
  124. return;
  125. }
  126. }
  127. } else {
  128. // Second keyevent within 0.2 second,
  129. // with the same key as the first one.
  130. if (CACurrentMediaTime() - lastCommand < 0.2 && lastInput == key.input && lastModifierFlags == key.modifierFlags) {
  131. for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance].commands) {
  132. if ([command.keyCommand.input isEqualToString:key.input] &&
  133. command.keyCommand.modifierFlags == key.modifierFlags && command.block) {
  134. // NOTE: throttle the key handler because on iOS 9 the handleKeyCommand:
  135. // method gets called repeatedly if the command key is held down.
  136. if (CACurrentMediaTime() - lastDoubleCommand > 0.5) {
  137. command.block(key);
  138. lastDoubleCommand = CACurrentMediaTime();
  139. }
  140. firstPress = YES;
  141. return;
  142. }
  143. }
  144. }
  145. lastCommand = CACurrentMediaTime();
  146. lastInput = key.input;
  147. lastModifierFlags = key.modifierFlags;
  148. }
  149. }
  150. @end
  151. @implementation RCTKeyCommands
  152. + (void)initialize
  153. {
  154. // swizzle UIResponder
  155. RCTSwapInstanceMethods([UIResponder class], @selector(keyCommands), @selector(RCT_keyCommands));
  156. }
  157. + (instancetype)sharedInstance
  158. {
  159. static RCTKeyCommands *sharedInstance;
  160. static dispatch_once_t onceToken;
  161. dispatch_once(&onceToken, ^{
  162. sharedInstance = [self new];
  163. });
  164. return sharedInstance;
  165. }
  166. - (instancetype)init
  167. {
  168. if ((self = [super init])) {
  169. _commands = [NSMutableSet new];
  170. }
  171. return self;
  172. }
  173. - (void)registerKeyCommandWithInput:(NSString *)input
  174. modifierFlags:(UIKeyModifierFlags)flags
  175. action:(void (^)(UIKeyCommand *))block
  176. {
  177. RCTAssertMainQueue();
  178. UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input
  179. modifierFlags:flags
  180. action:@selector(RCT_handleKeyCommand:)];
  181. RCTKeyCommand *keyCommand = [[RCTKeyCommand alloc] initWithKeyCommand:command block:block];
  182. [_commands removeObject:keyCommand];
  183. [_commands addObject:keyCommand];
  184. }
  185. - (void)unregisterKeyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags
  186. {
  187. RCTAssertMainQueue();
  188. for (RCTKeyCommand *command in _commands.allObjects) {
  189. if ([command matchesInput:input flags:flags]) {
  190. [_commands removeObject:command];
  191. break;
  192. }
  193. }
  194. }
  195. - (BOOL)isKeyCommandRegisteredForInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags
  196. {
  197. RCTAssertMainQueue();
  198. for (RCTKeyCommand *command in _commands) {
  199. if ([command matchesInput:input flags:flags]) {
  200. return YES;
  201. }
  202. }
  203. return NO;
  204. }
  205. - (void)registerDoublePressKeyCommandWithInput:(NSString *)input
  206. modifierFlags:(UIKeyModifierFlags)flags
  207. action:(void (^)(UIKeyCommand *))block
  208. {
  209. RCTAssertMainQueue();
  210. UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input
  211. modifierFlags:flags
  212. action:@selector(RCT_handleDoublePressKeyCommand:)];
  213. RCTKeyCommand *keyCommand = [[RCTKeyCommand alloc] initWithKeyCommand:command block:block];
  214. [_commands removeObject:keyCommand];
  215. [_commands addObject:keyCommand];
  216. }
  217. - (void)unregisterDoublePressKeyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags
  218. {
  219. RCTAssertMainQueue();
  220. for (RCTKeyCommand *command in _commands.allObjects) {
  221. if ([command matchesInput:input flags:flags]) {
  222. [_commands removeObject:command];
  223. break;
  224. }
  225. }
  226. }
  227. - (BOOL)isDoublePressKeyCommandRegisteredForInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags
  228. {
  229. RCTAssertMainQueue();
  230. for (RCTKeyCommand *command in _commands) {
  231. if ([command matchesInput:input flags:flags]) {
  232. return YES;
  233. }
  234. }
  235. return NO;
  236. }
  237. @end
  238. #else
  239. @implementation RCTKeyCommands
  240. + (instancetype)sharedInstance
  241. {
  242. return nil;
  243. }
  244. - (void)registerKeyCommandWithInput:(NSString *)input
  245. modifierFlags:(UIKeyModifierFlags)flags
  246. action:(void (^)(UIKeyCommand *))block
  247. {
  248. }
  249. - (void)unregisterKeyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags
  250. {
  251. }
  252. - (BOOL)isKeyCommandRegisteredForInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags
  253. {
  254. return NO;
  255. }
  256. - (void)registerDoublePressKeyCommandWithInput:(NSString *)input
  257. modifierFlags:(UIKeyModifierFlags)flags
  258. action:(void (^)(UIKeyCommand *))block
  259. {
  260. }
  261. - (void)unregisterDoublePressKeyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags
  262. {
  263. }
  264. - (BOOL)isDoublePressKeyCommandRegisteredForInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags
  265. {
  266. return NO;
  267. }
  268. @end
  269. #endif