1111
1212#import < UIKit/UIKit.h>
1313
14+ #import " RCTDefines.h"
1415#import " RCTUtils.h"
1516
17+ #if RCT_DEV
18+
19+ static BOOL RCTIsIOS8OrEarlier ()
20+ {
21+ return [UIDevice currentDevice ].systemVersion .floatValue < 9 ;
22+ }
23+
1624@interface RCTKeyCommand : NSObject <NSCopying >
1725
1826@property (nonatomic , strong ) UIKeyCommand *keyCommand;
@@ -27,7 +35,7 @@ - (instancetype)initWithKeyCommand:(UIKeyCommand *)keyCommand
2735{
2836 if ((self = [super init ])) {
2937 _keyCommand = keyCommand;
30- _block = block ?: ^(__unused UIKeyCommand *cmd) {} ;
38+ _block = block;
3139 }
3240 return self;
3341}
@@ -58,29 +66,58 @@ - (BOOL)matchesInput:(NSString *)input flags:(UIKeyModifierFlags)flags
5866 return [_keyCommand.input isEqual: input] && _keyCommand.modifierFlags == flags;
5967}
6068
69+ - (NSString *)description
70+ {
71+ return [NSString stringWithFormat: @" <%@ :%p input=\" %@ \" flags=%zd hasBlock=%@ >" ,
72+ [self class ], self , _keyCommand.input, _keyCommand.modifierFlags,
73+ _block ? @" YES" : @" NO" ];
74+ }
75+
6176@end
6277
6378@interface RCTKeyCommands ()
6479
6580@property (nonatomic , strong ) NSMutableSet *commands;
6681
67- - (BOOL )RCT_handleKeyCommand : (UIKeyCommand *)key ;
68-
6982@end
7083
71- @implementation UIApplication (RCTKeyCommands)
84+ @implementation UIResponder (RCTKeyCommands)
7285
7386- (NSArray *)RCT_keyCommands
7487{
7588 NSSet *commands = [RCTKeyCommands sharedInstance ].commands ;
76- return [[self RCT_keyCommands ] arrayByAddingObjectsFromArray:
77- [[commands valueForKeyPath: @" keyCommand" ] allObjects ]];
89+ return [[commands valueForKeyPath: @" keyCommand" ] allObjects ];
90+ }
91+
92+ - (void )RCT_handleKeyCommand : (UIKeyCommand *)key
93+ {
94+ // NOTE: throttle the key handler because on iOS 9 the handleKeyCommand:
95+ // method gets called repeatedly if the command key is held down.
96+
97+ static NSTimeInterval lastCommand = 0 ;
98+ if (RCTIsIOS8OrEarlier () || CACurrentMediaTime () - lastCommand > 0.5 ) {
99+ for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance ].commands ) {
100+ if ([command.keyCommand.input isEqualToString: key.input] &&
101+ command.keyCommand .modifierFlags == key.modifierFlags ) {
102+ if (command.block ) {
103+ command.block (key);
104+ lastCommand = CACurrentMediaTime ();
105+ }
106+ }
107+ }
108+ }
78109}
79110
111+ @end
112+
113+ @implementation UIApplication (RCTKeyCommands)
114+
115+ // Required for iOS 8.x
80116- (BOOL )RCT_sendAction : (SEL )action to : (id )target from : (id )sender forEvent : (UIEvent *)event
81117{
82118 if (action == @selector (RCT_handleKeyCommand: )) {
83- return [[RCTKeyCommands sharedInstance ] RCT_handleKeyCommand: sender];
119+ [self RCT_handleKeyCommand: sender];
120+ return YES ;
84121 }
85122 return [self RCT_sendAction: action to: target from: sender forEvent: event];
86123}
@@ -91,9 +128,23 @@ @implementation RCTKeyCommands
91128
92129+ (void )initialize
93130{
94- // swizzle UIApplication
95- RCTSwapInstanceMethods ([UIApplication class ], @selector (keyCommands ), @selector (RCT_keyCommands ));
96- RCTSwapInstanceMethods ([UIApplication class ], @selector (sendAction:to:from:forEvent: ), @selector (RCT_sendAction:to:from:forEvent: ));
131+ if (RCTIsIOS8OrEarlier ()) {
132+
133+ // swizzle UIApplication
134+ RCTSwapInstanceMethods ([UIApplication class ],
135+ @selector (keyCommands ),
136+ @selector (RCT_keyCommands ));
137+
138+ RCTSwapInstanceMethods ([UIApplication class ],
139+ @selector (sendAction:to:from:forEvent: ),
140+ @selector (RCT_sendAction:to:from:forEvent: ));
141+ } else {
142+
143+ // swizzle UIResponder
144+ RCTSwapInstanceMethods ([UIResponder class ],
145+ @selector (keyCommands ),
146+ @selector (RCT_keyCommands ));
147+ }
97148}
98149
99150+ (instancetype )sharedInstance
@@ -121,11 +172,11 @@ - (void)registerKeyCommandWithInput:(NSString *)input
121172{
122173 RCTAssertMainThread ();
123174
124- if (input.length && flags) {
175+ if (input.length && flags && RCTIsIOS8OrEarlier () ) {
125176
126177 // Workaround around the first cmd not working: http://openradar.appspot.com/19613391
127178 // You can register just the cmd key and do nothing. This ensures that
128- // command-key modified commands will work first time.
179+ // command-key modified commands will work first time. Fixed in iOS 9.
129180
130181 [self registerKeyCommandWithInput: @" "
131182 modifierFlags: flags
@@ -136,19 +187,9 @@ - (void)registerKeyCommandWithInput:(NSString *)input
136187 modifierFlags: flags
137188 action: @selector (RCT_handleKeyCommand: )];
138189
139- [_commands addObject: [[RCTKeyCommand alloc ] initWithKeyCommand: command block: block]];
140- }
141-
142- - (BOOL )RCT_handleKeyCommand : (UIKeyCommand *)key
143- {
144- for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance ].commands ) {
145- if ([command.keyCommand.input isEqualToString: key.input] &&
146- command.keyCommand .modifierFlags == key.modifierFlags ) {
147- command.block (key);
148- return YES ;
149- }
150- }
151- return NO ;
190+ RCTKeyCommand *keyCommand = [[RCTKeyCommand alloc ] initWithKeyCommand: command block: block];
191+ [_commands removeObject: keyCommand];
192+ [_commands addObject: keyCommand];
152193}
153194
154195- (void )unregisterKeyCommandWithInput : (NSString *)input
@@ -178,3 +219,29 @@ - (BOOL)isKeyCommandRegisteredForInput:(NSString *)input
178219}
179220
180221@end
222+
223+ #else
224+
225+ @implementation RCTKeyCommands
226+
227+ + (instancetype )sharedInstance
228+ {
229+ return nil ;
230+ }
231+
232+ - (void )registerKeyCommandWithInput : (NSString *)input
233+ modifierFlags : (UIKeyModifierFlags)flags
234+ action : (void (^)(UIKeyCommand *))block {}
235+
236+ - (void )unregisterKeyCommandWithInput : (NSString *)input
237+ modifierFlags : (UIKeyModifierFlags)flags {}
238+
239+ - (BOOL )isKeyCommandRegisteredForInput : (NSString *)input
240+ modifierFlags : (UIKeyModifierFlags)flags
241+ {
242+ return NO ;
243+ }
244+
245+ @end
246+
247+ #endif
0 commit comments