Introduce InputAccessoryView
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:
Родитель
23fbfc1977
Коммит
3d4535a2bb
|
@ -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
|
||||
*/
|
||||
|
|
Загрузка…
Ссылка в новой задаче