Support Input Accessory View (iOS Only) [1/N]

Reviewed By: mmmulani

Differential Revision: D6886573

fbshipit-source-id: 71e1f812b1cc1698e4380211a6cedd59011b5495
This commit is contained in:
Peter Argany
2018-02-27 10:42:44 -08:00
committed by Facebook Github Bot
parent c87d03a8b2
commit 38197c8230
13 changed files with 427 additions and 6 deletions

View File

@@ -46,6 +46,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, copy) RCTTextSelection *selection;
@property (nonatomic, strong, nullable) NSNumber *maxLength;
@property (nonatomic, copy) NSAttributedString *attributedText;
@property (nonatomic, copy) NSString *inputAccessoryViewID;
@end

View File

@@ -15,6 +15,8 @@
#import <React/RCTUtils.h>
#import <React/UIView+React.h>
#import "RCTInputAccessoryView.h"
#import "RCTInputAccessoryViewContent.h"
#import "RCTTextAttributes.h"
#import "RCTTextSelection.h"
@@ -400,12 +402,33 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
- (void)didSetProps:(NSArray<NSString *> *)changedProps
{
[self invalidateInputAccessoryView];
#if !TARGET_OS_TV
if ([changedProps containsObject:@"inputAccessoryViewID"] && self.inputAccessoryViewID) {
[self setCustomInputAccessoryViewWithNativeID:self.inputAccessoryViewID];
} else if (!self.inputAccessoryViewID) {
[self setDefaultInputAccessoryView];
}
#endif
}
- (void)invalidateInputAccessoryView
- (void)setCustomInputAccessoryViewWithNativeID:(NSString *)nativeID
{
__weak RCTBaseTextInputView *weakSelf = self;
[_bridge.uiManager rootViewForReactTag:self.reactTag withCompletion:^(UIView *rootView) {
RCTBaseTextInputView *strongSelf = weakSelf;
if (rootView) {
UIView *accessoryView = [strongSelf->_bridge.uiManager viewForNativeID:nativeID
withRootTag:rootView.reactTag];
if (accessoryView && [accessoryView isKindOfClass:[RCTInputAccessoryView class]]) {
strongSelf.backedTextInputView.inputAccessoryView = ((RCTInputAccessoryView *)accessoryView).content.inputAccessoryView;
[strongSelf reloadInputViewsIfNecessary];
}
}
}];
}
- (void)setDefaultInputAccessoryView
{
#if !TARGET_OS_TV
UIView<RCTBackedTextInputViewProtocol> *textInputView = self.backedTextInputView;
UIKeyboardType keyboardType = textInputView.keyboardType;
@@ -443,12 +466,15 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
else {
textInputView.inputAccessoryView = nil;
}
[self reloadInputViewsIfNecessary];
}
- (void)reloadInputViewsIfNecessary
{
// We have to call `reloadInputViews` for focused text inputs to update an accessory view.
if (textInputView.isFirstResponder) {
[textInputView reloadInputViews];
if (self.backedTextInputView.isFirstResponder) {
[self.backedTextInputView reloadInputViews];
}
#endif
}
- (void)handleInputAccessoryDoneButton

View File

@@ -53,6 +53,7 @@ RCT_EXPORT_VIEW_PROPERTY(clearTextOnFocus, BOOL)
RCT_EXPORT_VIEW_PROPERTY(maxLength, NSNumber)
RCT_EXPORT_VIEW_PROPERTY(selectTextOnFocus, BOOL)
RCT_EXPORT_VIEW_PROPERTY(selection, RCTTextSelection)
RCT_EXPORT_VIEW_PROPERTY(inputAccessoryViewID, NSString)
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock)

View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
@class RCTBridge;
@class RCTInputAccessoryViewContent;
@interface RCTInputAccessoryView : UIView
- (instancetype)initWithBridge:(RCTBridge *)bridge;
@property (nonatomic, readonly, strong) RCTInputAccessoryViewContent *content;
@end

View File

@@ -0,0 +1,69 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTInputAccessoryView.h"
#import <React/RCTBridge.h>
#import <React/RCTTouchHandler.h>
#import <React/UIView+React.h>
#import "RCTInputAccessoryViewContent.h"
@implementation RCTInputAccessoryView
{
BOOL _contentShouldBeFirstResponder;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if (self = [super init]) {
_content = [RCTInputAccessoryViewContent new];
RCTTouchHandler *const touchHandler = [[RCTTouchHandler alloc] initWithBridge:bridge];
[touchHandler attachToView:_content.inputAccessoryView];
[self addSubview:_content];
}
return self;
}
- (void)reactSetFrame:(CGRect)frame
{
[_content.inputAccessoryView setFrame:frame];
[_content.contentView setFrame:frame];
if (_contentShouldBeFirstResponder) {
_contentShouldBeFirstResponder = NO;
[_content becomeFirstResponder];
}
}
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)index
{
[super insertReactSubview:subview atIndex:index];
[_content insertReactSubview:subview atIndex:index];
}
- (void)removeReactSubview:(UIView *)subview
{
[super removeReactSubview:subview];
[_content removeReactSubview:subview];
}
- (void)didUpdateReactSubviews
{
// Do nothing, as subviews are managed by `insertReactSubview:atIndex:`
}
- (void)didSetProps:(NSArray<NSString *> *)changedProps
{
// If the accessory view is not linked to a text input via nativeID, assume it is
// a standalone component that should get focus whenever it is rendered
if (![changedProps containsObject:@"nativeID"] && !self.nativeID) {
_contentShouldBeFirstResponder = YES;
}
}
@end

View File

@@ -0,0 +1,14 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
@interface RCTInputAccessoryViewContent : UIView
@property (nonatomic, readwrite, retain) UIView *contentView;
@end

View File

@@ -0,0 +1,75 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTInputAccessoryViewContent.h"
#import <React/UIView+React.h>
@interface RCTInputAccessoryViewContent()
// Overriding `inputAccessoryView` to `readwrite`.
@property (nonatomic, readwrite, retain) UIView *inputAccessoryView;
@end
@implementation RCTInputAccessoryViewContent
- (BOOL)canBecomeFirstResponder
{
return true;
}
- (BOOL)becomeFirstResponder
{
const BOOL becameFirstResponder = [super becomeFirstResponder];
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
// Avoiding the home pill and notch (landscape mode) on iphoneX.
if (becameFirstResponder) {
if (@available(iOS 11.0, *)) {
[_contentView.bottomAnchor
constraintLessThanOrEqualToSystemSpacingBelowAnchor:_contentView.window.safeAreaLayoutGuide.bottomAnchor
multiplier:1.0f].active = YES;
[_contentView.leftAnchor
constraintLessThanOrEqualToSystemSpacingAfterAnchor:_contentView.window.safeAreaLayoutGuide.leftAnchor
multiplier:1.0f].active = YES;
[_contentView.rightAnchor
constraintLessThanOrEqualToSystemSpacingAfterAnchor:_contentView.window.safeAreaLayoutGuide.rightAnchor
multiplier:1.0f].active = YES;
}
}
#endif
return becameFirstResponder;
}
- (UIView *)inputAccessoryView
{
if (!_inputAccessoryView) {
_inputAccessoryView = [UIView new];
_contentView = [UIView new];
[_inputAccessoryView addSubview:_contentView];
}
return _inputAccessoryView;
}
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)index
{
[super insertReactSubview:subview atIndex:index];
[_contentView insertSubview:subview atIndex:index];
}
- (void)removeReactSubview:(UIView *)subview
{
[super removeReactSubview:subview];
[subview removeFromSuperview];
if ([[_inputAccessoryView subviews] count] == 0 && [self isFirstResponder]) {
[self resignFirstResponder];
}
}
@end

View File

@@ -0,0 +1,12 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTViewManager.h>
@interface RCTInputAccessoryViewManager : RCTViewManager
@end

View File

@@ -0,0 +1,28 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTInputAccessoryViewManager.h"
#import "RCTInputAccessoryView.h"
@implementation RCTInputAccessoryViewManager
RCT_EXPORT_MODULE()
+ (BOOL)requiresMainQueueSetup
{
return NO;
}
- (UIView *)view
{
return [[RCTInputAccessoryView alloc] initWithBridge:self.bridge];
}
RCT_REMAP_VIEW_PROPERTY(backgroundColor, content.inputAccessoryView.backgroundColor, UIColor)
@end