Summary:
Changelog: [Internal]

Introducing InputAccessoryView.

There is one big difference between Fabric's implementation and Paper's implementation.

Fabric searches for text input from InputAccessoryView, unlike Paper where it is the other way around.

Reviewed By: shergin

Differential Revision: D22160445

fbshipit-source-id: 55313fe50afeced7aead5b57137d711dd1cfd3ae
This commit is contained in:
Samuel Susla 2020-06-30 01:33:52 -07:00 коммит произвёл Facebook GitHub Bot
Родитель 23fbfc1977
Коммит 3d4535a2bb
18 изменённых файлов: 458 добавлений и 4 удалений

Просмотреть файл

@ -21,6 +21,8 @@ type NativeProps = $ReadOnly<{|
backgroundColor?: ?ColorValue,
|}>;
export default (codegenNativeComponent<NativeProps>(
'RCTInputAccessoryView',
): HostComponent<NativeProps>);
export default (codegenNativeComponent<NativeProps>('InputAccessory', {
interfaceOnly: true,
paperComponentName: 'RCTInputAccessoryView',
excludedPlatforms: ['android'],
}): HostComponent<NativeProps>);

Просмотреть файл

@ -37,6 +37,8 @@ NS_ASSUME_NONNULL_BEGIN
// it's declared here only to conform to the interface.
@property (nonatomic, assign) BOOL caretHidden;
@property (nonatomic, strong, nullable) NSString *inputAccessoryViewID;
@end
NS_ASSUME_NONNULL_END

Просмотреть файл

@ -30,6 +30,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, assign) BOOL enablesReturnKeyAutomatically;
@property (nonatomic, assign) UITextFieldViewMode clearButtonMode;
@property (nonatomic, getter=isScrollEnabled) BOOL scrollEnabled;
@property (nonatomic, strong, nullable) NSString *inputAccessoryViewID;
// This protocol disallows direct access to `selectedTextRange` property because
// unwise usage of it can break the `delegate` behavior. So, we always have to

Просмотреть файл

@ -28,6 +28,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, assign) UIEdgeInsets textContainerInset;
@property (nonatomic, assign, getter=isEditable) BOOL editable;
@property (nonatomic, getter=isScrollEnabled) BOOL scrollEnabled;
@property (nonatomic, strong, nullable) NSString *inputAccessoryViewID;
@end

Просмотреть файл

@ -0,0 +1,21 @@
/*
* 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 <UIKit/UIKit.h>
#import <React/RCTViewComponentView.h>
NS_ASSUME_NONNULL_BEGIN
/**
* UIView class for root <InputAccessoryView> component.
*/
@interface RCTInputAccessoryComponentView : RCTViewComponentView
@end
NS_ASSUME_NONNULL_END

Просмотреть файл

@ -0,0 +1,153 @@
/*
* 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 "RCTInputAccessoryComponentView.h"
#import <React/RCTBackedTextInputViewProtocol.h>
#import <React/RCTConversions.h>
#import <React/RCTSurfaceTouchHandler.h>
#import <React/UIView+React.h>
#import <react/components/inputaccessory/InputAccessoryComponentDescriptor.h>
#import <react/components/rncore/Props.h>
#import "RCTInputAccessoryContentView.h"
#import "RCTFabricComponentsPlugins.h"
using namespace facebook::react;
static UIView<RCTBackedTextInputViewProtocol> *_Nullable RCTFindTextInputWithNativeId(UIView *view, NSString *nativeId)
{
if ([view respondsToSelector:@selector(inputAccessoryViewID)] &&
[view respondsToSelector:@selector(setInputAccessoryView:)]) {
UIView<RCTBackedTextInputViewProtocol> *typed = (UIView<RCTBackedTextInputViewProtocol> *)view;
if (!nativeId || [typed.inputAccessoryViewID isEqualToString:nativeId]) {
return typed;
}
}
for (UIView *subview in view.subviews) {
UIView<RCTBackedTextInputViewProtocol> *result = RCTFindTextInputWithNativeId(subview, nativeId);
if (result) {
return result;
}
}
return nil;
}
@implementation RCTInputAccessoryComponentView {
InputAccessoryShadowNode::ConcreteState::Shared _state;
RCTInputAccessoryContentView *_contentView;
RCTSurfaceTouchHandler *_touchHandler;
UIView<RCTBackedTextInputViewProtocol> __weak *_textInput;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static const auto defaultProps = std::make_shared<const InputAccessoryProps>();
_props = defaultProps;
_contentView = [RCTInputAccessoryContentView new];
_touchHandler = [RCTSurfaceTouchHandler new];
[_touchHandler attachToView:_contentView];
}
return self;
}
- (void)didMoveToWindow
{
[super didMoveToWindow];
if (self.window && !_textInput) {
if (self.nativeId) {
_textInput = RCTFindTextInputWithNativeId(self.window, self.nativeId);
_textInput.inputAccessoryView = _contentView;
} else {
_textInput = RCTFindTextInputWithNativeId(_contentView, nil);
}
if (!self.nativeId) {
[self becomeFirstResponder];
}
}
}
- (BOOL)canBecomeFirstResponder
{
return true;
}
- (UIView *)inputAccessoryView
{
return _contentView;
}
#pragma mark - RCTComponentViewProtocol
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<InputAccessoryComponentDescriptor>();
}
- (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
{
[_contentView insertSubview:childComponentView atIndex:index];
}
- (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
{
[childComponentView removeFromSuperview];
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
auto const &oldInputAccessoryProps = *std::static_pointer_cast<InputAccessoryProps const>(_props);
auto const &newInputAccessoryProps = *std::static_pointer_cast<InputAccessoryProps const>(props);
if (newInputAccessoryProps.backgroundColor != oldInputAccessoryProps.backgroundColor) {
_contentView.backgroundColor = RCTUIColorFromSharedColor(newInputAccessoryProps.backgroundColor);
}
[super updateProps:props oldProps:oldProps];
self.hidden = true;
}
- (void)updateState:(const facebook::react::State::Shared &)state
oldState:(const facebook::react::State::Shared &)oldState
{
_state = std::static_pointer_cast<InputAccessoryShadowNode::ConcreteState const>(state);
CGSize oldScreenSize = RCTCGSizeFromSize(_state->getData().screenSize);
CGSize screenSize = [[UIScreen mainScreen] bounds].size;
screenSize.height = std::nan("");
if (oldScreenSize.width != screenSize.width) {
auto stateData = InputAccessoryState{RCTSizeFromCGSize(screenSize)};
_state->updateState(std::move(stateData));
}
}
- (void)updateLayoutMetrics:(const facebook::react::LayoutMetrics &)layoutMetrics
oldLayoutMetrics:(const facebook::react::LayoutMetrics &)oldLayoutMetrics
{
[super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
[_contentView setFrame:RCTCGRectFromRect(layoutMetrics.getContentFrame())];
}
- (void)prepareForRecycle
{
[super prepareForRecycle];
_state.reset();
_textInput = nil;
}
@end
Class<RCTComponentViewProtocol> RCTInputAccessoryCls(void)
{
return RCTInputAccessoryComponentView.class;
}

Просмотреть файл

@ -0,0 +1,12 @@
/*
* 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 <UIKit/UIKit.h>
@interface RCTInputAccessoryContentView : UIView
@end

Просмотреть файл

@ -0,0 +1,70 @@
/*
* 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 "RCTInputAccessoryContentView.h"
@implementation RCTInputAccessoryContentView {
UIView *_safeAreaContainer;
NSLayoutConstraint *_heightConstraint;
}
- (instancetype)init
{
if (self = [super init]) {
self.autoresizingMask = UIViewAutoresizingFlexibleHeight;
_safeAreaContainer = [UIView new];
_safeAreaContainer.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:_safeAreaContainer];
_heightConstraint = [_safeAreaContainer.heightAnchor constraintEqualToConstant:0];
_heightConstraint.active = YES;
if (@available(iOS 11.0, tvOS 11.0, *)) {
[NSLayoutConstraint activateConstraints:@[
[_safeAreaContainer.bottomAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.bottomAnchor],
[_safeAreaContainer.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor],
[_safeAreaContainer.leadingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.leadingAnchor],
[_safeAreaContainer.trailingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.trailingAnchor]
]];
} else {
[NSLayoutConstraint activateConstraints:@[
[_safeAreaContainer.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
[_safeAreaContainer.topAnchor constraintEqualToAnchor:self.topAnchor],
[_safeAreaContainer.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
[_safeAreaContainer.trailingAnchor constraintEqualToAnchor:self.trailingAnchor]
]];
}
}
return self;
}
- (CGSize)intrinsicContentSize
{
// This is needed so the view size is based on autolayout constraints.
return CGSizeZero;
}
- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index
{
[_safeAreaContainer insertSubview:view atIndex:index];
}
- (void)setFrame:(CGRect)frame
{
[super setFrame:frame];
[_safeAreaContainer setFrame:frame];
_heightConstraint.constant = frame.size.height;
[self layoutIfNeeded];
}
- (BOOL)canBecomeFirstResponder
{
return true;
}
@end

Просмотреть файл

@ -40,6 +40,7 @@ Class<RCTComponentViewProtocol> RCTModalHostViewCls(void) __attribute__((used));
Class<RCTComponentViewProtocol> RCTImageCls(void) __attribute__((used));
Class<RCTComponentViewProtocol> RCTParagraphCls(void) __attribute__((used));
Class<RCTComponentViewProtocol> RCTTextInputCls(void) __attribute__((used));
Class<RCTComponentViewProtocol> RCTInputAccessoryCls(void) __attribute__((used));
Class<RCTComponentViewProtocol> RCTViewCls(void) __attribute__((used));
#ifdef __cplusplus

Просмотреть файл

@ -29,6 +29,7 @@ Class<RCTComponentViewProtocol> RCTFabricComponentsProvider(const char *name) {
{"Image", RCTImageCls},
{"Paragraph", RCTParagraphCls},
{"TextInput", RCTTextInputCls},
{"InputAccessoryView", RCTInputAccessoryCls},
{"View", RCTViewCls},
};

Просмотреть файл

@ -207,6 +207,10 @@ using namespace facebook::react;
_backedTextInputView.tintColor = RCTUIColorFromSharedColor(newTextInputProps.selectionColor);
}
if (newTextInputProps.inputAccessoryViewID != oldTextInputProps.inputAccessoryViewID) {
_backedTextInputView.inputAccessoryViewID = RCTNSStringFromString(newTextInputProps.inputAccessoryViewID);
}
[super updateProps:props oldProps:oldProps];
}

Просмотреть файл

@ -0,0 +1,47 @@
load("@fbsource//tools/build_defs/apple:flag_defs.bzl", "get_preprocessor_flags_for_build_mode")
load(
"//tools/build_defs/oss:rn_defs.bzl",
"APPLE",
"get_apple_compiler_flags",
"get_apple_inspector_flags",
"react_native_xplat_target",
"rn_xplat_cxx_library",
"subdir_glob",
)
APPLE_COMPILER_FLAGS = get_apple_compiler_flags()
rn_xplat_cxx_library(
name = "inputaccessory",
srcs = glob(
["**/*.cpp"],
),
headers = [],
header_namespace = "",
exported_headers = subdir_glob(
[
("", "*.h"),
],
prefix = "react/components/inputaccessory",
),
compiler_flags = [
"-fexceptions",
"-frtti",
"-std=c++14",
"-Wall",
],
fbobjc_compiler_flags = APPLE_COMPILER_FLAGS,
fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(),
force_static = True,
labels = ["supermodule:xplat/default/public.react_native.infra"],
platforms = (APPLE),
preprocessor_flags = [
"-DLOG_TAG=\"ReactNative\"",
"-DWITH_FBSYSTRACE=1",
],
visibility = ["PUBLIC"],
deps = [
react_native_xplat_target("fabric/core:core"),
"//xplat/js/react-native-github:generated_components-rncore",
],
)

Просмотреть файл

@ -0,0 +1,48 @@
/*
* 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.
*/
#pragma once
#include <react/components/inputaccessory/InputAccessoryShadowNode.h>
#include <react/core/ConcreteComponentDescriptor.h>
namespace facebook {
namespace react {
/*
* Descriptor for <InputAccessoryView> component.
*/
class InputAccessoryComponentDescriptor final
: public ConcreteComponentDescriptor<InputAccessoryShadowNode> {
public:
using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
void adopt(UnsharedShadowNode shadowNode) const override {
assert(std::dynamic_pointer_cast<InputAccessoryShadowNode>(shadowNode));
auto concreteShadowNode =
std::static_pointer_cast<InputAccessoryShadowNode>(shadowNode);
assert(std::dynamic_pointer_cast<YogaLayoutableShadowNode>(
concreteShadowNode));
auto layoutableShadowNode =
std::static_pointer_cast<YogaLayoutableShadowNode>(concreteShadowNode);
auto state =
std::static_pointer_cast<const InputAccessoryShadowNode::ConcreteState>(
shadowNode->getState());
auto stateData = state->getData();
layoutableShadowNode->setSize(
Size{stateData.screenSize.width, stateData.screenSize.height});
layoutableShadowNode->setPositionType(YGPositionTypeAbsolute);
ConcreteComponentDescriptor::adopt(shadowNode);
}
};
} // namespace react
} // namespace facebook

Просмотреть файл

@ -0,0 +1,16 @@
/*
* 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.
*/
#include "InputAccessoryShadowNode.h"
namespace facebook {
namespace react {
extern const char InputAccessoryComponentName[] = "InputAccessoryView";
} // namespace react
} // namespace facebook

Просмотреть файл

@ -0,0 +1,39 @@
/*
* 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.
*/
#pragma once
#include <react/components/inputaccessory/InputAccessoryState.h>
#include <react/components/rncore/EventEmitters.h>
#include <react/components/rncore/Props.h>
#include <react/components/view/ConcreteViewShadowNode.h>
namespace facebook {
namespace react {
extern const char InputAccessoryComponentName[];
/*
* `ShadowNode` for <InputAccessory> component.
*/
class InputAccessoryShadowNode final : public ConcreteViewShadowNode<
InputAccessoryComponentName,
InputAccessoryProps,
InputAccessoryEventEmitter,
InputAccessoryState> {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
static ShadowNodeTraits BaseTraits() {
auto traits = ConcreteViewShadowNode::BaseTraits();
traits.set(ShadowNodeTraits::Trait::RootNodeKind);
return traits;
}
};
} // namespace react
} // namespace facebook

Просмотреть файл

@ -0,0 +1,29 @@
/*
* 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.
*/
#pragma once
#include <react/graphics/Float.h>
#include <react/graphics/Geometry.h>
#include <react/graphics/conversions.h>
namespace facebook {
namespace react {
/*
* State for <InputAccessoryView> component.
*/
class InputAccessoryState final {
public:
InputAccessoryState(){};
InputAccessoryState(Size screenSize_) : screenSize(screenSize_){};
const Size screenSize{};
};
} // namespace react
} // namespace facebook

Просмотреть файл

@ -56,7 +56,12 @@ TextInputProps::TextInputProps(
sourceProps.mostRecentEventCount,
{})),
autoFocus(
convertRawProp(rawProps, "autoFocus", sourceProps.autoFocus, {})){};
convertRawProp(rawProps, "autoFocus", sourceProps.autoFocus, {})),
inputAccessoryViewID(convertRawProp(
rawProps,
"inputAccessoryViewID",
sourceProps.inputAccessoryViewID,
{})){};
TextAttributes TextInputProps::getEffectiveTextAttributes(
Float fontSizeMultiplier) const {

Просмотреть файл

@ -55,6 +55,8 @@ class TextInputProps final : public ViewProps, public BaseTextProps {
bool autoFocus{false};
std::string const inputAccessoryViewID{};
/*
* Accessors
*/