mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-05-10 01:15:00 +08:00
Support Input Accessory View (iOS Only) [1/N]
Reviewed By: mmmulani Differential Revision: D6886573 fbshipit-source-id: 71e1f812b1cc1698e4380211a6cedd59011b5495
This commit is contained in:
committed by
Facebook Github Bot
parent
c87d03a8b2
commit
38197c8230
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
19
Libraries/Text/TextInput/RCTInputAccessoryView.h
Normal file
19
Libraries/Text/TextInput/RCTInputAccessoryView.h
Normal 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
|
||||
69
Libraries/Text/TextInput/RCTInputAccessoryView.m
Normal file
69
Libraries/Text/TextInput/RCTInputAccessoryView.m
Normal 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
|
||||
14
Libraries/Text/TextInput/RCTInputAccessoryViewContent.h
Normal file
14
Libraries/Text/TextInput/RCTInputAccessoryViewContent.h
Normal 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
|
||||
75
Libraries/Text/TextInput/RCTInputAccessoryViewContent.m
Normal file
75
Libraries/Text/TextInput/RCTInputAccessoryViewContent.m
Normal 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
|
||||
12
Libraries/Text/TextInput/RCTInputAccessoryViewManager.h
Normal file
12
Libraries/Text/TextInput/RCTInputAccessoryViewManager.h
Normal 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
|
||||
28
Libraries/Text/TextInput/RCTInputAccessoryViewManager.m
Normal file
28
Libraries/Text/TextInput/RCTInputAccessoryViewManager.m
Normal 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
|
||||
Reference in New Issue
Block a user