react-native/React/Modules/RCTKeyboardObserver.m
nossbigg 05f35c296d Expose isLocalUserInfoKey to keyboard event notifications (#23245)
Summary:
Given two apps loaded side-by-side and when a `Keyboard` event is triggered, there is no way to ascertain which app triggered the keyboard event. This ambiguity can arise in slide over/split view scenarios.

This pull request exposes the `isLocalUserInfoKey` property of the native `UIKeyboard` iOS events to the `Keyboard` event listener; this property will return `true` for the app that triggered the keyboard event.

(Also, I threw in a couple of Keyboard.js tests just for fun 😅)

[iOS][Added] - Expose isLocalUserInfoKey to keyboard event notifications

1. Load two apps side-by-side, with the app on the left side subscribing to the keyboard events (and logging out the events as they happen)
1. Trigger a keyboard to appear with the left app. The logged keyboard event will contain the `isEventFromThisApp` property which will be true.
1. Dismiss the keyboard
1. Trigger a keyboard to appear with the right app. The left app will still log the keyboard event, but the event's `isEventFromThisApp` property will be false (because the left app didn't trigger the keyboard event)
Pull Request resolved: https://github.com/facebook/react-native/pull/23245

Differential Revision: D13928612

Pulled By: hramos

fbshipit-source-id: 6d74d2565e2af62328485fd9da86f15f9e2ccfab
2019-02-01 14:32:19 -08:00

124 lines
3.6 KiB
Objective-C

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTKeyboardObserver.h"
#import "RCTEventDispatcher.h"
static NSDictionary *RCTParseKeyboardNotification(NSNotification *notification);
@implementation RCTKeyboardObserver
RCT_EXPORT_MODULE()
- (void)startObserving
{
#if !TARGET_OS_TV
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
#define ADD_KEYBOARD_HANDLER(NAME, SELECTOR) \
[nc addObserver:self selector:@selector(SELECTOR:) name:NAME object:nil]
ADD_KEYBOARD_HANDLER(UIKeyboardWillShowNotification, keyboardWillShow);
ADD_KEYBOARD_HANDLER(UIKeyboardDidShowNotification, keyboardDidShow);
ADD_KEYBOARD_HANDLER(UIKeyboardWillHideNotification, keyboardWillHide);
ADD_KEYBOARD_HANDLER(UIKeyboardDidHideNotification, keyboardDidHide);
ADD_KEYBOARD_HANDLER(UIKeyboardWillChangeFrameNotification, keyboardWillChangeFrame);
ADD_KEYBOARD_HANDLER(UIKeyboardDidChangeFrameNotification, keyboardDidChangeFrame);
#undef ADD_KEYBOARD_HANDLER
#endif
}
- (NSArray<NSString *> *)supportedEvents
{
return @[@"keyboardWillShow",
@"keyboardDidShow",
@"keyboardWillHide",
@"keyboardDidHide",
@"keyboardWillChangeFrame",
@"keyboardDidChangeFrame"];
}
- (void)stopObserving
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
// Bridge might be already invalidated by the time the keyboard is about to be dismissed.
// This might happen, for example, when reload from the packager is performed.
// Thus we need to check against nil here.
#define IMPLEMENT_KEYBOARD_HANDLER(EVENT) \
- (void)EVENT:(NSNotification *)notification \
{ \
if (!self.bridge) { \
return; \
} \
[self sendEventWithName:@#EVENT \
body:RCTParseKeyboardNotification(notification)]; \
}
IMPLEMENT_KEYBOARD_HANDLER(keyboardWillShow)
IMPLEMENT_KEYBOARD_HANDLER(keyboardDidShow)
IMPLEMENT_KEYBOARD_HANDLER(keyboardWillHide)
IMPLEMENT_KEYBOARD_HANDLER(keyboardDidHide)
IMPLEMENT_KEYBOARD_HANDLER(keyboardWillChangeFrame)
IMPLEMENT_KEYBOARD_HANDLER(keyboardDidChangeFrame)
@end
NS_INLINE NSDictionary *RCTRectDictionaryValue(CGRect rect)
{
return @{
@"screenX": @(rect.origin.x),
@"screenY": @(rect.origin.y),
@"width": @(rect.size.width),
@"height": @(rect.size.height),
};
}
static NSString *RCTAnimationNameForCurve(UIViewAnimationCurve curve)
{
switch (curve) {
case UIViewAnimationCurveEaseIn:
return @"easeIn";
case UIViewAnimationCurveEaseInOut:
return @"easeInEaseOut";
case UIViewAnimationCurveEaseOut:
return @"easeOut";
case UIViewAnimationCurveLinear:
return @"linear";
default:
return @"keyboard";
}
}
static NSDictionary *RCTParseKeyboardNotification(NSNotification *notification)
{
#if TARGET_OS_TV
return @{};
#else
NSDictionary *userInfo = notification.userInfo;
CGRect beginFrame = [userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
CGRect endFrame = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
NSTimeInterval duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
UIViewAnimationCurve curve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];
NSInteger isLocalUserInfoKey = [userInfo[UIKeyboardIsLocalUserInfoKey] integerValue];
return @{
@"startCoordinates": RCTRectDictionaryValue(beginFrame),
@"endCoordinates": RCTRectDictionaryValue(endFrame),
@"duration": @(duration * 1000.0), // ms
@"easing": RCTAnimationNameForCurve(curve),
@"isEventFromThisApp": isLocalUserInfoKey == 1 ? @YES : @NO,
};
#endif
}