This commit is contained in:
Yamozha
2021-04-02 02:24:13 +03:00
parent c23950b545
commit 7256d79e2c
31493 changed files with 3036630 additions and 0 deletions

View File

@ -0,0 +1,14 @@
/*
* 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.
*/
namespace facebook {
namespace react {
extern const char RCTARTSurfaceViewComponentName[] = "ARTSurfaceView";
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,27 @@
/*
* 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/view/ConcreteViewShadowNode.h>
#include "RCTARTSurfaceViewProps.h"
namespace facebook {
namespace react {
extern const char RCTARTSurfaceViewComponentName[];
/*
* `ShadowNode` for <ARTSurfaceView> component.
*/
using RCTARTSurfaceShadowNode = ConcreteViewShadowNode<
RCTARTSurfaceViewComponentName,
RCTARTSurfaceViewProps,
ViewEventEmitter>;
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,20 @@
/*
* 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/core/ConcreteComponentDescriptor.h>
#include "RCTARTSurfaceShadowNode.h"
namespace facebook {
namespace react {
using RCTARTSurfaceComponentDescriptor =
ConcreteComponentDescriptor<RCTARTSurfaceShadowNode>;
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,15 @@
/*
* 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 <React/RCTViewComponentView.h>
/**
* UIView class for root <ARTSurfaceView> component.
*/
@interface RCTARTSurfaceViewComponentView : RCTViewComponentView
@end

View File

@ -0,0 +1,41 @@
/*
* 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 "RCTARTSurfaceViewComponentView.h"
#import <react/uimanager/ComponentDescriptorProvider.h>
#import "RCTARTSurfaceViewComponentDescriptor.h"
#import "FBRCTFabricComponentsPlugins.h"
using namespace facebook::react;
@implementation RCTARTSurfaceViewComponentView {
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static const auto defaultProps = std::make_shared<const RCTARTSurfaceViewProps>();
_props = defaultProps;
}
return self;
}
#pragma mark - RCTComponentViewProtocol
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<RCTARTSurfaceComponentDescriptor>();
}
@end
Class<RCTComponentViewProtocol> RCTARTSurfaceViewCls(void)
{
return RCTARTSurfaceViewComponentView.class;
}

View File

@ -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.
*/
#include "RCTARTSurfaceViewProps.h"
#include <react/core/propsConversions.h>
namespace facebook {
namespace react {
RCTARTSurfaceViewProps::RCTARTSurfaceViewProps(
const RCTARTSurfaceViewProps &sourceProps,
const RawProps &rawProps)
: ViewProps(sourceProps, rawProps) {}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,26 @@
/*
* 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/view/ViewProps.h>
namespace facebook {
namespace react {
class RCTARTSurfaceViewProps final : public ViewProps {
public:
RCTARTSurfaceViewProps() = default;
RCTARTSurfaceViewProps(
const RCTARTSurfaceViewProps &sourceProps,
const RawProps &rawProps);
#pragma mark - Props
};
} // namespace react
} // namespace facebook

View File

@ -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 <ActivityIndicator> component.
*/
@interface RCTActivityIndicatorViewComponentView : RCTViewComponentView
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,97 @@
/*
* 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 "RCTActivityIndicatorViewComponentView.h"
#import <react/components/rncore/ComponentDescriptors.h>
#import <react/components/rncore/EventEmitters.h>
#import <react/components/rncore/Props.h>
#import "FBRCTFabricComponentsPlugins.h"
using namespace facebook::react;
static UIActivityIndicatorViewStyle convertActivityIndicatorViewStyle(const ActivityIndicatorViewSize &size)
{
switch (size) {
case ActivityIndicatorViewSize::Small:
return UIActivityIndicatorViewStyleWhite;
case ActivityIndicatorViewSize::Large:
return UIActivityIndicatorViewStyleWhiteLarge;
}
}
@implementation RCTActivityIndicatorViewComponentView {
UIActivityIndicatorView *_activityIndicatorView;
}
#pragma mark - RCTComponentViewProtocol
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<ActivityIndicatorViewComponentDescriptor>();
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static const auto defaultProps = std::make_shared<const ActivityIndicatorViewProps>();
_props = defaultProps;
_activityIndicatorView = [[UIActivityIndicatorView alloc] initWithFrame:self.bounds];
_activityIndicatorView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
if (defaultProps->animating) {
[_activityIndicatorView startAnimating];
} else {
[_activityIndicatorView stopAnimating];
}
_activityIndicatorView.color = [UIColor colorWithCGColor:defaultProps->color.get()];
_activityIndicatorView.hidesWhenStopped = defaultProps->hidesWhenStopped;
_activityIndicatorView.activityIndicatorViewStyle = convertActivityIndicatorViewStyle(defaultProps->size);
[self addSubview:_activityIndicatorView];
}
return self;
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
const auto &oldViewProps = *std::static_pointer_cast<const ActivityIndicatorViewProps>(_props);
const auto &newViewProps = *std::static_pointer_cast<const ActivityIndicatorViewProps>(props);
if (oldViewProps.animating != newViewProps.animating) {
if (newViewProps.animating) {
[_activityIndicatorView startAnimating];
} else {
[_activityIndicatorView stopAnimating];
}
}
if (oldViewProps.color.get() != newViewProps.color.get()) {
_activityIndicatorView.color = [UIColor colorWithCGColor:newViewProps.color.get()];
}
// TODO: This prop should be deprecated.
if (oldViewProps.hidesWhenStopped != newViewProps.hidesWhenStopped) {
_activityIndicatorView.hidesWhenStopped = newViewProps.hidesWhenStopped;
}
if (oldViewProps.size != newViewProps.size) {
_activityIndicatorView.activityIndicatorViewStyle = convertActivityIndicatorViewStyle(newViewProps.size);
}
[super updateProps:props oldProps:oldProps];
}
@end
Class<RCTComponentViewProtocol> RCTActivityIndicatorViewCls(void)
{
return RCTActivityIndicatorViewComponentView.class;
}

View File

@ -0,0 +1,19 @@
/*
* 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 <React/RCTViewComponentView.h>
NS_ASSUME_NONNULL_BEGIN
/**
* UIView class for root <Image> component.
*/
@interface RCTImageComponentView : RCTViewComponentView
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,204 @@
/*
* 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 "RCTImageComponentView.h"
#import <React/RCTImageResponseDelegate.h>
#import <React/RCTImageResponseObserverProxy.h>
#import <react/components/image/ImageComponentDescriptor.h>
#import <react/components/image/ImageEventEmitter.h>
#import <react/components/image/ImageProps.h>
#import <react/imagemanager/ImageInstrumentation.h>
#import <react/imagemanager/ImageRequest.h>
#import <react/imagemanager/RCTImageInstrumentationProxy.h>
#import <react/imagemanager/RCTImagePrimitivesConversions.h>
#import "RCTConversions.h"
#import "RCTFabricComponentsPlugins.h"
using namespace facebook::react;
@interface RCTImageComponentView () <RCTImageResponseDelegate>
@end
@implementation RCTImageComponentView {
UIImageView *_imageView;
ImageShadowNode::ConcreteState::Shared _state;
ImageResponseObserverCoordinator const *_coordinator;
RCTImageResponseObserverProxy _imageResponseObserverProxy;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static auto const defaultProps = std::make_shared<ImageProps const>();
_props = defaultProps;
_imageView = [[UIImageView alloc] initWithFrame:self.bounds];
_imageView.clipsToBounds = YES;
_imageView.contentMode = (UIViewContentMode)RCTResizeModeFromImageResizeMode(defaultProps->resizeMode);
_imageResponseObserverProxy = RCTImageResponseObserverProxy(self);
self.contentView = _imageView;
}
return self;
}
#pragma mark - RCTComponentViewProtocol
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<ImageComponentDescriptor>();
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
auto const &oldImageProps = *std::static_pointer_cast<ImageProps const>(_props);
auto const &newImageProps = *std::static_pointer_cast<ImageProps const>(props);
// `resizeMode`
if (oldImageProps.resizeMode != newImageProps.resizeMode) {
if (newImageProps.resizeMode == ImageResizeMode::Repeat) {
// Repeat resize mode is handled by the UIImage. Use scale to fill
// so the repeated image fills the UIImageView.
_imageView.contentMode = UIViewContentModeScaleToFill;
} else {
_imageView.contentMode = (UIViewContentMode)RCTResizeModeFromImageResizeMode(newImageProps.resizeMode);
}
}
// `tintColor`
if (oldImageProps.tintColor != newImageProps.tintColor) {
_imageView.tintColor = [UIColor colorWithCGColor:newImageProps.tintColor.get()];
}
[super updateProps:props oldProps:oldProps];
}
- (void)updateState:(State::Shared const &)state oldState:(State::Shared const &)oldState
{
_state = std::static_pointer_cast<ImageShadowNode::ConcreteState const>(state);
auto _oldState = std::static_pointer_cast<ImageShadowNode::ConcreteState const>(oldState);
auto data = _state->getData();
// This call (setting `coordinator`) must be unconditional (at the same block as setting `State`)
// because the setter stores a raw pointer to object that `State` owns.
self.coordinator = &data.getImageRequest().getObserverCoordinator();
bool havePreviousData = _oldState && _oldState->getData().getImageSource() != ImageSource{};
if (!havePreviousData || data.getImageSource() != _oldState->getData().getImageSource()) {
// Loading actually starts a little before this, but this is the first time we know
// the image is loading and can fire an event from this component
std::static_pointer_cast<ImageEventEmitter const>(_eventEmitter)->onLoadStart();
// TODO (T58941612): Tracking for visibility should be done directly on this class.
// For now, we consolidate instrumentation logic in the image loader, so that pre-Fabric gets the same treatment.
auto instrumentation = std::static_pointer_cast<RCTImageInstrumentationProxy const>(
data.getImageRequest().getSharedImageInstrumentation());
if (instrumentation) {
instrumentation->trackNativeImageView(self);
}
}
}
- (void)setCoordinator:(ImageResponseObserverCoordinator const *)coordinator
{
if (_coordinator) {
_coordinator->removeObserver(_imageResponseObserverProxy);
}
_coordinator = coordinator;
if (_coordinator != nullptr) {
_coordinator->addObserver(_imageResponseObserverProxy);
}
}
- (void)prepareForRecycle
{
[super prepareForRecycle];
self.coordinator = nullptr;
_imageView.image = nil;
_state.reset();
}
- (void)dealloc
{
self.coordinator = nullptr;
}
#pragma mark - RCTImageResponseDelegate
- (void)didReceiveImage:(UIImage *)image fromObserver:(void const *)observer
{
if (!_eventEmitter || !_state) {
// Notifications are delivered asynchronously and might arrive after the view is already recycled.
// In the future, we should incorporate an `EventEmitter` into a separate object owned by `ImageRequest` or `State`.
// See for more info: T46311063.
return;
}
std::static_pointer_cast<ImageEventEmitter const>(_eventEmitter)->onLoad();
std::static_pointer_cast<ImageEventEmitter const>(_eventEmitter)->onLoadEnd();
const auto &imageProps = *std::static_pointer_cast<ImageProps const>(_props);
if (imageProps.tintColor) {
image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
}
if (imageProps.resizeMode == ImageResizeMode::Repeat) {
image = [image resizableImageWithCapInsets:RCTUIEdgeInsetsFromEdgeInsets(imageProps.capInsets)
resizingMode:UIImageResizingModeTile];
} else if (imageProps.capInsets != EdgeInsets()) {
// Applying capInsets of 0 will switch the "resizingMode" of the image to "tile" which is undesired.
image = [image resizableImageWithCapInsets:RCTUIEdgeInsetsFromEdgeInsets(imageProps.capInsets)
resizingMode:UIImageResizingModeStretch];
}
self->_imageView.image = image;
// Apply trilinear filtering to smooth out mis-sized images.
self->_imageView.layer.minificationFilter = kCAFilterTrilinear;
self->_imageView.layer.magnificationFilter = kCAFilterTrilinear;
auto data = _state->getData();
auto instrumentation = std::static_pointer_cast<RCTImageInstrumentationProxy const>(
data.getImageRequest().getSharedImageInstrumentation());
if (instrumentation) {
instrumentation->didSetImage();
}
}
- (void)didReceiveProgress:(float)progress fromObserver:(void const *)observer
{
if (!_eventEmitter) {
return;
}
std::static_pointer_cast<ImageEventEmitter const>(_eventEmitter)->onProgress(progress);
}
- (void)didReceiveFailureFromObserver:(void const *)observer
{
_imageView.image = nil;
if (!_eventEmitter) {
return;
}
std::static_pointer_cast<ImageEventEmitter const>(_eventEmitter)->onError();
std::static_pointer_cast<ImageEventEmitter const>(_eventEmitter)->onLoadEnd();
}
@end
Class<RCTComponentViewProtocol> RCTImageCls(void)
{
return RCTImageComponentView.class;
}

View File

@ -0,0 +1,25 @@
/*
* 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
@interface RCTLegacyViewManagerInteropComponentView : RCTViewComponentView
/**
Returns true for components that are supported by LegacyViewManagerInterop layer, false otherwise.
*/
+ (BOOL)isSupported:(NSString *)componentName;
+ (void)supportLegacyViewManagerWithName:(NSString *)componentName;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,148 @@
/*
* 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 "RCTLegacyViewManagerInteropComponentView.h"
#import <React/UIView+React.h>
#import <react/components/legacyviewmanagerinterop/LegacyViewManagerInteropComponentDescriptor.h>
#import <react/components/legacyviewmanagerinterop/LegacyViewManagerInteropViewProps.h>
#import <react/utils/ManagedObjectWrapper.h>
#import "RCTLegacyViewManagerInteropCoordinatorAdapter.h"
using namespace facebook::react;
@implementation RCTLegacyViewManagerInteropComponentView {
NSMutableDictionary<NSNumber *, UIView *> *_viewsToBeMounted;
NSMutableArray<UIView *> *_viewsToBeUnmounted;
RCTLegacyViewManagerInteropCoordinatorAdapter *_adapter;
LegacyViewManagerInteropShadowNode::ConcreteState::Shared _state;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static const auto defaultProps = std::make_shared<const LegacyViewManagerInteropViewProps>();
_props = defaultProps;
_viewsToBeMounted = [NSMutableDictionary new];
_viewsToBeUnmounted = [NSMutableArray new];
}
return self;
}
+ (NSMutableSet<NSString *> *)supportedViewManagers
{
static NSMutableSet<NSString *> *supported =
[NSMutableSet setWithObjects:@"Picker", @"DatePicker", @"ProgressView", @"SegmentedControl", @"MaskedView", nil];
return supported;
}
+ (BOOL)isSupported:(NSString *)componentName
{
return [[RCTLegacyViewManagerInteropComponentView supportedViewManagers] containsObject:componentName];
}
+ (void)supportLegacyViewManagerWithName:(NSString *)componentName
{
[[RCTLegacyViewManagerInteropComponentView supportedViewManagers] addObject:componentName];
}
- (RCTLegacyViewManagerInteropCoordinator *)coordinator
{
if (_state != nullptr) {
const auto &state = _state->getData();
return unwrapManagedObject(state.coordinator);
} else {
return nil;
}
}
- (NSString *)componentViewName_DO_NOT_USE_THIS_IS_BROKEN
{
const auto &state = _state->getData();
RCTLegacyViewManagerInteropCoordinator *coordinator = unwrapManagedObject(state.coordinator);
return coordinator.componentViewName;
}
#pragma mark - RCTComponentViewProtocol
- (void)prepareForRecycle
{
_adapter = nil;
[_viewsToBeMounted removeAllObjects];
[_viewsToBeUnmounted removeAllObjects];
_state.reset();
[super prepareForRecycle];
}
- (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
{
[_viewsToBeMounted setObject:childComponentView forKey:[NSNumber numberWithInteger:index]];
}
- (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
{
[_viewsToBeUnmounted addObject:childComponentView];
}
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<LegacyViewManagerInteropComponentDescriptor>();
}
- (void)updateState:(State::Shared const &)state oldState:(State::Shared const &)oldState
{
_state = std::static_pointer_cast<LegacyViewManagerInteropShadowNode::ConcreteState const>(state);
}
- (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask
{
[super finalizeUpdates:updateMask];
if (!_adapter) {
_adapter = [[RCTLegacyViewManagerInteropCoordinatorAdapter alloc] initWithCoordinator:self.coordinator
reactTag:self.tag];
__weak __typeof(self) weakSelf = self;
_adapter.eventInterceptor = ^(std::string eventName, folly::dynamic event) {
if (weakSelf) {
__typeof(self) strongSelf = weakSelf;
auto eventEmitter =
std::static_pointer_cast<LegacyViewManagerInteropViewEventEmitter const>(strongSelf->_eventEmitter);
eventEmitter->dispatchEvent(eventName, event);
}
};
self.contentView = _adapter.paperView;
}
for (NSNumber *key in _viewsToBeMounted) {
[_adapter.paperView insertReactSubview:_viewsToBeMounted[key] atIndex:key.integerValue];
}
[_viewsToBeMounted removeAllObjects];
for (UIView *view in _viewsToBeUnmounted) {
[_adapter.paperView removeReactSubview:view];
}
[_viewsToBeUnmounted removeAllObjects];
[_adapter.paperView didUpdateReactSubviews];
if (updateMask & RNComponentViewUpdateMaskProps) {
const auto &newProps = *std::static_pointer_cast<const LegacyViewManagerInteropViewProps>(_props);
[_adapter setProps:newProps.otherProps];
}
}
#pragma mark - Native Commands
- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
{
[_adapter handleCommand:(NSString *)commandName args:(NSArray *)args];
}
@end

View File

@ -0,0 +1,27 @@
/*
* 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 <Foundation/Foundation.h>
#import <react/components/legacyviewmanagerinterop/RCTLegacyViewManagerInteropCoordinator.h>
NS_ASSUME_NONNULL_BEGIN
@interface RCTLegacyViewManagerInteropCoordinatorAdapter : NSObject
- (instancetype)initWithCoordinator:(RCTLegacyViewManagerInteropCoordinator *)coordinator reactTag:(NSInteger)tag;
@property (strong, nonatomic) UIView *paperView;
@property (nonatomic, copy, nullable) void (^eventInterceptor)(std::string eventName, folly::dynamic event);
- (void)setProps:(folly::dynamic const &)props;
- (void)handleCommand:(NSString *)commandName args:(NSArray *)args;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,58 @@
/*
* 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 "RCTLegacyViewManagerInteropCoordinatorAdapter.h"
#import <React/UIView+React.h>
@implementation RCTLegacyViewManagerInteropCoordinatorAdapter {
RCTLegacyViewManagerInteropCoordinator *_coordinator;
NSInteger _tag;
}
- (instancetype)initWithCoordinator:(RCTLegacyViewManagerInteropCoordinator *)coordinator reactTag:(NSInteger)tag
{
if (self = [super init]) {
_coordinator = coordinator;
_tag = tag;
}
return self;
}
- (void)dealloc
{
[_paperView removeFromSuperview];
[_coordinator removeObserveForTag:_tag];
}
- (UIView *)paperView
{
if (!_paperView) {
_paperView = _coordinator.paperView;
_paperView.reactTag = [NSNumber numberWithInteger:_tag];
__weak __typeof(self) weakSelf = self;
[_coordinator addObserveForTag:_tag
usingBlock:^(std::string eventName, folly::dynamic event) {
if (weakSelf.eventInterceptor) {
weakSelf.eventInterceptor(eventName, event);
}
}];
}
return _paperView;
}
- (void)setProps:(const folly::dynamic &)props
{
[_coordinator setProps:props forView:self.paperView];
}
- (void)handleCommand:(NSString *)commandName args:(NSArray *)args
{
[_coordinator handleCommand:commandName args:args reactTag:_tag];
}
@end

View File

@ -0,0 +1,22 @@
/*
* 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>
@protocol RCTFabricModalHostViewControllerDelegate <NSObject>
- (void)boundsDidChange:(CGRect)newBounds;
@end
@interface RCTFabricModalHostViewController : UIViewController
@property (nonatomic, weak) id<RCTFabricModalHostViewControllerDelegate> delegate;
#if !TARGET_OS_TV
@property (nonatomic, assign) UIInterfaceOrientationMask supportedInterfaceOrientations;
#endif
@end

View File

@ -0,0 +1,94 @@
/*
* 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 "RCTFabricModalHostViewController.h"
#import <React/RCTLog.h>
#import <React/RCTSurfaceTouchHandler.h>
@implementation RCTFabricModalHostViewController {
CGRect _lastViewBounds;
RCTSurfaceTouchHandler *_touchHandler;
}
- (instancetype)init
{
if (!(self = [super init])) {
return nil;
}
_touchHandler = [RCTSurfaceTouchHandler new];
return self;
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
if (!CGRectEqualToRect(_lastViewBounds, self.view.bounds)) {
[_delegate boundsDidChange:self.view.bounds];
_lastViewBounds = self.view.bounds;
}
}
- (void)loadView
{
[super loadView];
[_touchHandler attachToView:self.view];
}
#if !TARGET_OS_TV
- (UIStatusBarStyle)preferredStatusBarStyle
{
return [RCTSharedApplication() statusBarStyle];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
_lastViewBounds = CGRectZero;
}
- (BOOL)prefersStatusBarHidden
{
return [RCTSharedApplication() isStatusBarHidden];
}
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)())completion
{
UIView *snapshot = [self.view snapshotViewAfterScreenUpdates:NO];
[self.view addSubview:snapshot];
[super dismissViewControllerAnimated:flag
completion:^{
[snapshot removeFromSuperview];
if (completion) {
completion();
}
}];
}
#if RCT_DEV
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
UIInterfaceOrientationMask appSupportedOrientationsMask =
[RCTSharedApplication() supportedInterfaceOrientationsForWindow:[RCTSharedApplication() keyWindow]];
if (!(_supportedInterfaceOrientations & appSupportedOrientationsMask)) {
RCTLogError(
@"Modal was presented with 0x%x orientations mask but the application only supports 0x%x."
@"Add more interface orientations to your app's Info.plist to fix this."
@"NOTE: This will crash in non-dev mode.",
(unsigned)_supportedInterfaceOrientations,
(unsigned)appSupportedOrientationsMask);
return UIInterfaceOrientationMaskAll;
}
return _supportedInterfaceOrientations;
}
#endif // RCT_DEV
#endif // !TARGET_OS_TV
@end

View File

@ -0,0 +1,15 @@
/*
* 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 <React/RCTViewComponentView.h>
/**
* UIView class for root <ModalHostView> component.
*/
@interface RCTModalHostViewComponentView : RCTViewComponentView
@end

View File

@ -0,0 +1,231 @@
/*
* 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 "RCTModalHostViewComponentView.h"
#import <React/UIView+React.h>
#import <react/components/modal/ModalHostViewComponentDescriptor.h>
#import <react/components/modal/ModalHostViewState.h>
#import <react/components/rncore/EventEmitters.h>
#import <react/components/rncore/Props.h>
#import "FBRCTFabricComponentsPlugins.h"
#import "RCTConversions.h"
#import "RCTFabricModalHostViewController.h"
using namespace facebook::react;
#if !TARGET_OS_TV
static UIInterfaceOrientationMask supportedOrientationsMask(ModalHostViewSupportedOrientationsMask mask)
{
UIInterfaceOrientationMask supportedOrientations = 0;
if (mask & ModalHostViewSupportedOrientations::Portrait) {
supportedOrientations |= UIInterfaceOrientationMaskPortrait;
}
if (mask & ModalHostViewSupportedOrientations::PortraitUpsideDown) {
supportedOrientations |= UIInterfaceOrientationMaskPortraitUpsideDown;
}
if (mask & ModalHostViewSupportedOrientations::Landscape) {
supportedOrientations |= UIInterfaceOrientationMaskLandscape;
}
if (mask & ModalHostViewSupportedOrientations::LandscapeLeft) {
supportedOrientations |= UIInterfaceOrientationMaskLandscapeLeft;
}
if (mask & ModalHostViewSupportedOrientations::LandscapeRight) {
supportedOrientations |= UIInterfaceOrientationMaskLandscapeRight;
}
if (supportedOrientations == 0) {
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
return UIInterfaceOrientationMaskAll;
} else {
return UIInterfaceOrientationMaskPortrait;
}
}
return supportedOrientations;
}
#endif
static std::tuple<BOOL, UIModalTransitionStyle> animationConfiguration(ModalHostViewAnimationType const animation)
{
switch (animation) {
case ModalHostViewAnimationType::None:
return std::make_tuple(NO, UIModalTransitionStyleCoverVertical);
case ModalHostViewAnimationType::Slide:
return std::make_tuple(YES, UIModalTransitionStyleCoverVertical);
case ModalHostViewAnimationType::Fade:
return std::make_tuple(YES, UIModalTransitionStyleCrossDissolve);
}
}
static UIModalPresentationStyle presentationConfiguration(ModalHostViewProps const &props)
{
if (props.transparent) {
return UIModalPresentationOverFullScreen;
}
switch (props.presentationStyle) {
case ModalHostViewPresentationStyle::FullScreen:
return UIModalPresentationFullScreen;
case ModalHostViewPresentationStyle::PageSheet:
return UIModalPresentationPageSheet;
case ModalHostViewPresentationStyle::FormSheet:
return UIModalPresentationFormSheet;
case ModalHostViewPresentationStyle::OverFullScreen:
return UIModalPresentationOverFullScreen;
}
}
static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(CGRect rect)
{
;
auto orientation = rect.size.width < rect.size.height
? ModalHostViewEventEmitter::OnOrientationChangeOrientation::Portrait
: ModalHostViewEventEmitter::OnOrientationChangeOrientation::Landscape;
return {orientation};
}
@interface RCTModalHostViewComponentView () <RCTFabricModalHostViewControllerDelegate>
@end
@implementation RCTModalHostViewComponentView {
RCTFabricModalHostViewController *_viewController;
ModalHostViewShadowNode::ConcreteState::Shared _state;
BOOL _shouldAnimatePresentation;
BOOL _isPresented;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static const auto defaultProps = std::make_shared<const ModalHostViewProps>();
_props = defaultProps;
_shouldAnimatePresentation = YES;
_viewController = [RCTFabricModalHostViewController new];
_viewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
_viewController.delegate = self;
_isPresented = NO;
}
return self;
}
- (void)ensurePresentedOnlyIfNeeded
{
BOOL shouldBePresented = !_isPresented && self.window;
if (shouldBePresented) {
UIViewController *controller = [self reactViewController];
_isPresented = YES;
return [controller
presentViewController:_viewController
animated:_shouldAnimatePresentation
completion:^{
if (!self->_eventEmitter) {
return;
}
assert(std::dynamic_pointer_cast<ModalHostViewEventEmitter const>(self->_eventEmitter));
auto eventEmitter = std::static_pointer_cast<ModalHostViewEventEmitter const>(self->_eventEmitter);
eventEmitter->onShow(ModalHostViewEventEmitter::OnShow{});
}];
}
BOOL shouldBeHidden = _isPresented && !self.superview;
if (shouldBeHidden) {
_isPresented = NO;
[_viewController dismissViewControllerAnimated:_shouldAnimatePresentation completion:nil];
}
}
- (void)didMoveToWindow
{
[super didMoveToWindow];
[self ensurePresentedOnlyIfNeeded];
}
- (void)didMoveToSuperview
{
[super didMoveToSuperview];
[self ensurePresentedOnlyIfNeeded];
}
#pragma mark - RCTFabricModalHostViewControllerDelegate
- (void)boundsDidChange:(CGRect)newBounds
{
if (_eventEmitter) {
assert(std::dynamic_pointer_cast<ModalHostViewEventEmitter const>(_eventEmitter));
auto eventEmitter = std::static_pointer_cast<ModalHostViewEventEmitter const>(_eventEmitter);
eventEmitter->onOrientationChange(onOrientationChangeStruct(newBounds));
}
if (_state != nullptr) {
auto newState = ModalHostViewState{RCTSizeFromCGSize(newBounds.size)};
_state->updateState(std::move(newState));
}
}
#pragma mark - RCTComponentViewProtocol
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<ModalHostViewComponentDescriptor>();
}
- (void)prepareForRecycle
{
[super prepareForRecycle];
_state.reset();
_isPresented = NO;
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
const auto &newProps = *std::static_pointer_cast<const ModalHostViewProps>(props);
#if !TARGET_OS_TV
_viewController.supportedInterfaceOrientations = supportedOrientationsMask(newProps.supportedOrientations);
#endif
std::tuple<BOOL, UIModalTransitionStyle> result = animationConfiguration(newProps.animationType);
_shouldAnimatePresentation = std::get<0>(result);
_viewController.modalTransitionStyle = std::get<1>(result);
_viewController.modalPresentationStyle = presentationConfiguration(newProps);
[super updateProps:props oldProps:oldProps];
}
- (void)updateState:(facebook::react::State::Shared const &)state
oldState:(facebook::react::State::Shared const &)oldState
{
_state = std::static_pointer_cast<const ModalHostViewShadowNode::ConcreteState>(state);
}
- (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
{
[_viewController.view insertSubview:childComponentView atIndex:index];
}
- (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
{
[childComponentView removeFromSuperview];
}
@end
Class<RCTComponentViewProtocol> RCTModalHostViewCls(void)
{
return RCTModalHostViewComponentView.class;
}

View File

@ -0,0 +1,49 @@
/**
* 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.
*
* @generated by an internal plugin build system
*/
#ifdef RN_DISABLE_OSS_PLUGIN_HEADER
// FB Internal: FBRCTFabricComponentsPlugins.h is autogenerated by the build system.
#import <React/FBRCTFabricComponentsPlugins.h>
#else
// OSS-compatibility layer
#import <Foundation/Foundation.h>
#import <React/RCTComponentViewProtocol.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wreturn-type-c-linkage"
#ifdef __cplusplus
extern "C" {
#endif
Class<RCTComponentViewProtocol> RCTFabricComponentsProvider(const char *name);
// Lookup functions
Class<RCTComponentViewProtocol> RCTSafeAreaViewCls(void) __attribute__((used));
Class<RCTComponentViewProtocol> RCTScrollViewCls(void) __attribute__((used));
Class<RCTComponentViewProtocol> RCTPullToRefreshViewCls(void) __attribute__((used));
Class<RCTComponentViewProtocol> RCTActivityIndicatorViewCls(void) __attribute__((used));
Class<RCTComponentViewProtocol> RCTSliderCls(void) __attribute__((used));
Class<RCTComponentViewProtocol> RCTSwitchCls(void) __attribute__((used));
Class<RCTComponentViewProtocol> RCTUnimplementedNativeViewCls(void) __attribute__((used));
Class<RCTComponentViewProtocol> RCTModalHostViewCls(void) __attribute__((used));
Class<RCTComponentViewProtocol> RCTARTSurfaceViewCls(void) __attribute__((used));
Class<RCTComponentViewProtocol> RCTImageCls(void) __attribute__((used));
#ifdef __cplusplus
}
#endif
#pragma GCC diagnostic pop
#endif // RN_DISABLE_OSS_PLUGIN_HEADER

View File

@ -0,0 +1,41 @@
/**
* 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.
*
* @generated by an internal plugin build system
*/
#ifndef RN_DISABLE_OSS_PLUGIN_HEADER
// OSS-compatibility layer
#import "RCTFabricComponentsPlugins.h"
#import <string>
#import <unordered_map>
Class<RCTComponentViewProtocol> RCTFabricComponentsProvider(const char *name) {
static std::unordered_map<std::string, Class (*)(void)> sFabricComponentsClassMap = {
{"SafeAreaView", RCTSafeAreaViewCls},
{"ScrollView", RCTScrollViewCls},
{"PullToRefreshView", RCTPullToRefreshViewCls},
{"ActivityIndicatorView", RCTActivityIndicatorViewCls},
{"Slider", RCTSliderCls},
{"Switch", RCTSwitchCls},
{"UnimplementedNativeView", RCTUnimplementedNativeViewCls},
{"ModalHostView", RCTModalHostViewCls},
{"ARTSurfaceView", RCTARTSurfaceViewCls},
{"Image", RCTImageCls},
};
auto p = sFabricComponentsClassMap.find(name);
if (p != sFabricComponentsClassMap.end()) {
auto classFunc = p->second;
return classFunc();
}
return nil;
}
#endif // RN_DISABLE_OSS_PLUGIN_HEADER

View File

@ -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 <View> component.
*/
@interface RCTRootComponentView : RCTViewComponentView
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,34 @@
/*
* 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 "RCTRootComponentView.h"
#import <react/components/root/RootComponentDescriptor.h>
#import <react/components/root/RootProps.h>
using namespace facebook::react;
@implementation RCTRootComponentView
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static const auto defaultProps = std::make_shared<const RootProps>();
_props = defaultProps;
}
return self;
}
#pragma mark - RCTComponentViewProtocol
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<RootComponentDescriptor>();
}
@end

View File

@ -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 <SafeAreaView> component.
*/
@interface RCTSafeAreaViewComponentView : RCTViewComponentView
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,99 @@
/*
* 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 "RCTSafeAreaViewComponentView.h"
#import <React/RCTUtils.h>
#import <react/components/safeareaview/SafeAreaViewComponentDescriptor.h>
#import <react/components/safeareaview/SafeAreaViewState.h>
#import "FBRCTFabricComponentsPlugins.h"
#import "RCTConversions.h"
#import "RCTFabricComponentsPlugins.h"
using namespace facebook::react;
@implementation RCTSafeAreaViewComponentView {
SafeAreaViewShadowNode::ConcreteState::Shared _state;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static auto const defaultProps = std::make_shared<SafeAreaViewProps const>();
_props = defaultProps;
self.clipsToBounds = YES;
}
return self;
}
- (UIEdgeInsets)_safeAreaInsets
{
if (@available(iOS 11.0, tvOS 11.0, *)) {
return self.safeAreaInsets;
}
return UIEdgeInsetsZero;
}
- (void)layoutSubviews
{
[super layoutSubviews];
}
- (void)safeAreaInsetsDidChange
{
[super safeAreaInsetsDidChange];
[self _updateStateIfNecessary];
}
- (void)_updateStateIfNecessary
{
if (!_state) {
return;
}
UIEdgeInsets insets = [self _safeAreaInsets];
insets.left = RCTRoundPixelValue(insets.left);
insets.top = RCTRoundPixelValue(insets.top);
insets.right = RCTRoundPixelValue(insets.right);
insets.bottom = RCTRoundPixelValue(insets.bottom);
auto oldPadding = _state->getData().padding;
auto newPadding = RCTEdgeInsetsFromUIEdgeInsets(insets);
auto threshold = 1.0 / RCTScreenScale() + 0.01; // Size of a pixel plus some small threshold.
auto deltaPadding = newPadding - oldPadding;
if (std::abs(deltaPadding.left) < threshold && std::abs(deltaPadding.top) < threshold &&
std::abs(deltaPadding.right) < threshold && std::abs(deltaPadding.bottom) < threshold) {
return;
}
_state->updateState(SafeAreaViewState{newPadding});
}
#pragma mark - RCTComponentViewProtocol
- (void)updateState:(facebook::react::State::Shared const &)state
oldState:(facebook::react::State::Shared const &)oldState
{
_state = std::static_pointer_cast<SafeAreaViewShadowNode::ConcreteState const>(state);
[self _updateStateIfNecessary];
}
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<SafeAreaViewComponentDescriptor>();
}
@end
Class<RCTComponentViewProtocol> RCTSafeAreaViewCls(void)
{
return RCTSafeAreaViewComponentView.class;
}

View File

@ -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.
*/
#import <UIKit/UIKit.h>
#import <React/RCTGenericDelegateSplitter.h>
#import <React/RCTViewComponentView.h>
NS_ASSUME_NONNULL_BEGIN
/*
* `UIScrollView` subclass which has some improvements and tweaks
* which are not directly related to React Native.
*/
@interface RCTEnhancedScrollView : UIScrollView
/*
* Returns a delegate splitter that can be used to create as many `UIScrollView` delegates as needed.
* Use that instead of accessing `delegate` property directly.
*
* This class overrides the `delegate` property and wires that to the delegate splitter.
*
* We never know which another part of the app might introspect the view hierarchy and mess with `UIScrollView`'s
* delegate, so we expose a fake delegate connected to the original one via the splitter to make the component as
* resilient to other code as possible: even if something else nil the delegate, other delegates that were subscribed
* via the splitter will continue working.
*/
@property (nonatomic, strong, readonly) RCTGenericDelegateSplitter<id<UIScrollViewDelegate>> *delegateSplitter;
@property (nonatomic, assign) BOOL pinchGestureEnabled;
@property (nonatomic, assign) BOOL centerContent;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,73 @@
/*
* 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 "RCTEnhancedScrollView.h"
@implementation RCTEnhancedScrollView {
__weak id<UIScrollViewDelegate> _publicDelegate;
}
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
if ([key isEqualToString:@"delegate"]) {
// For `delegate` property, we issue KVO notifications manually.
// We need that to block notifications caused by setting the original `UIScrollView`s property.
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
if (@available(iOS 11.0, *)) {
// We set the default behavior to "never" so that iOS
// doesn't do weird things to UIScrollView insets automatically
// and keeps it as an opt-in behavior.
self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
__weak __typeof(self) weakSelf = self;
_delegateSplitter = [[RCTGenericDelegateSplitter alloc] initWithDelegateUpdateBlock:^(id delegate) {
[weakSelf setPrivateDelegate:delegate];
}];
}
return self;
}
- (void)setPrivateDelegate:(id<UIScrollViewDelegate>)delegate
{
[super setDelegate:delegate];
}
- (id<UIScrollViewDelegate>)delegate
{
return _publicDelegate;
}
- (void)setDelegate:(id<UIScrollViewDelegate>)delegate
{
if (_publicDelegate == delegate) {
return;
}
if (_publicDelegate) {
[_delegateSplitter removeDelegate:_publicDelegate];
}
[self willChangeValueForKey:@"delegate"];
_publicDelegate = delegate;
[self didChangeValueForKey:@"delegate"];
if (_publicDelegate) {
[_delegateSplitter addDelegate:_publicDelegate];
}
}
@end

View File

@ -0,0 +1,23 @@
/*
* 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 <PullToRefreshView> component.
* This view is designed to only serve ViewController-like purpose for the actual `UIRefreshControl` view which is being
* attached to some `UIScrollView` (not to this view).
*/
@interface RCTPullToRefreshViewComponentView : RCTViewComponentView
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,190 @@
/*
* 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 "RCTPullToRefreshViewComponentView.h"
#import <react/components/rncore/ComponentDescriptors.h>
#import <react/components/rncore/EventEmitters.h>
#import <react/components/rncore/Props.h>
#import <react/components/rncore/RCTComponentViewHelpers.h>
#import <React/RCTConversions.h>
#import <React/RCTRefreshableProtocol.h>
#import <React/RCTScrollViewComponentView.h>
#import "FBRCTFabricComponentsPlugins.h"
using namespace facebook::react;
@interface RCTPullToRefreshViewComponentView () <RCTPullToRefreshViewViewProtocol, RCTRefreshableProtocol>
@end
@implementation RCTPullToRefreshViewComponentView {
UIRefreshControl *_refreshControl;
RCTScrollViewComponentView *_scrollViewComponentView;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
// This view is not designed to be visible, it only serves UIViewController-like purpose managing
// attaching and detaching of a pull-to-refresh view to a scroll view.
// The pull-to-refresh view is not a subview of this view.
self.hidden = YES;
static auto const defaultProps = std::make_shared<PullToRefreshViewProps const>();
_props = defaultProps;
_refreshControl = [[UIRefreshControl alloc] init];
[_refreshControl addTarget:self
action:@selector(handleUIControlEventValueChanged)
forControlEvents:UIControlEventValueChanged];
}
return self;
}
#pragma mark - RCTComponentViewProtocol
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<PullToRefreshViewComponentDescriptor>();
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
auto const &oldConcreteProps = *std::static_pointer_cast<PullToRefreshViewProps const>(_props);
auto const &newConcreteProps = *std::static_pointer_cast<PullToRefreshViewProps const>(props);
if (newConcreteProps.refreshing != oldConcreteProps.refreshing) {
if (newConcreteProps.refreshing) {
[_refreshControl beginRefreshing];
} else {
[_refreshControl endRefreshing];
}
}
BOOL needsUpdateTitle = NO;
if (newConcreteProps.title != oldConcreteProps.title) {
needsUpdateTitle = YES;
}
if (newConcreteProps.titleColor != oldConcreteProps.titleColor) {
needsUpdateTitle = YES;
}
if (needsUpdateTitle) {
[self _updateTitle];
}
[super updateProps:props oldProps:oldProps];
}
#pragma mark -
- (void)handleUIControlEventValueChanged
{
std::static_pointer_cast<PullToRefreshViewEventEmitter const>(_eventEmitter)->onRefresh({});
}
- (void)_updateTitle
{
auto const &concreteProps = *std::static_pointer_cast<PullToRefreshViewProps const>(_props);
if (concreteProps.title.empty()) {
_refreshControl.attributedTitle = nil;
return;
}
NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
if (concreteProps.titleColor) {
attributes[NSForegroundColorAttributeName] = RCTUIColorFromSharedColor(concreteProps.titleColor);
}
_refreshControl.attributedTitle =
[[NSAttributedString alloc] initWithString:RCTNSStringFromString(concreteProps.title) attributes:attributes];
}
#pragma mark - Attaching & Detaching
- (void)didMoveToWindow
{
if (self.window) {
[self _attach];
} else {
[self _detach];
}
}
- (void)_attach
{
if (_scrollViewComponentView) {
[self _detach];
}
_scrollViewComponentView = [RCTScrollViewComponentView findScrollViewComponentViewForView:self];
if (!_scrollViewComponentView) {
return;
}
if (@available(macOS 13.0, *)) {
_scrollViewComponentView.scrollView.refreshControl = _refreshControl;
}
}
- (void)_detach
{
if (!_scrollViewComponentView) {
return;
}
// iOS requires to end refreshing before unmounting.
[_refreshControl endRefreshing];
if (@available(macOS 13.0, *)) {
_scrollViewComponentView.scrollView.refreshControl = nil;
}
_scrollViewComponentView = nil;
}
#pragma mark - Native commands
- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
{
RCTPullToRefreshViewHandleCommand(self, commandName, args);
}
- (void)setNativeRefreshing:(BOOL)refreshing
{
if (refreshing) {
[_refreshControl beginRefreshing];
} else {
[_refreshControl endRefreshing];
}
}
#pragma mark - RCTRefreshableProtocol
- (void)setRefreshing:(BOOL)refreshing
{
[self setNativeRefreshing:refreshing];
}
#pragma mark -
- (NSString *)componentViewName_DO_NOT_USE_THIS_IS_BROKEN
{
return @"RefreshControl";
}
@end
Class<RCTComponentViewProtocol> RCTPullToRefreshViewCls(void)
{
return RCTPullToRefreshViewComponentView.class;
}

View File

@ -0,0 +1,60 @@
/*
* 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/RCTGenericDelegateSplitter.h>
#import <React/RCTScrollableProtocol.h>
#import <React/RCTViewComponentView.h>
NS_ASSUME_NONNULL_BEGIN
/*
* UIView class for <ScrollView> component.
*
* By design, the class does not implement any logic that contradicts to the normal behavior of UIScrollView and does
* not contain any special/custom support for things like floating headers, pull-to-refresh components,
* keyboard-avoiding functionality and so on. All that complexity must be implemented inside those components in order
* to keep the complexity of this component manageable.
*/
@interface RCTScrollViewComponentView : RCTViewComponentView
/*
* Finds and returns the closet RCTScrollViewComponentView component to the given view
*/
+ (RCTScrollViewComponentView *_Nullable)findScrollViewComponentViewForView:(UIView *)view;
/*
* Returns an actual UIScrollView that this component uses under the hood.
*/
@property (nonatomic, strong, readonly) UIScrollView *scrollView;
/*
* Returns the subview of the scroll view that the component uses to mount all subcomponents into. That's useful to
* separate component views from auxiliary views to be able to reliably implement pull-to-refresh- and RTL-related
* functionality.
*/
@property (nonatomic, strong, readonly) UIView *containerView;
/*
* Returns a delegate splitter that can be used to subscribe for UIScrollView delegate.
*/
@property (nonatomic, strong, readonly)
RCTGenericDelegateSplitter<id<UIScrollViewDelegate>> *scrollViewDelegateSplitter;
@end
/*
* RCTScrollableProtocol is a protocol which RCTScrollViewManager uses to communicate with all kinds of `UIScrollView`s.
* Until Fabric has own command-execution pipeline we have to support that to some extent. The implementation shouldn't
* be perfect though because very soon we will migrate that to the new commands infra and get rid of this.
*/
@interface RCTScrollViewComponentView (ScrollableProtocol) <RCTScrollableProtocol>
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,432 @@
/*
* 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 "RCTScrollViewComponentView.h"
#import <React/RCTAssert.h>
#import <React/RCTBridge+Private.h>
#import <React/RCTScrollEvent.h>
#import <react/components/scrollview/RCTComponentViewHelpers.h>
#import <react/components/scrollview/ScrollViewComponentDescriptor.h>
#import <react/components/scrollview/ScrollViewEventEmitter.h>
#import <react/components/scrollview/ScrollViewProps.h>
#import <react/components/scrollview/ScrollViewState.h>
#import <react/graphics/Geometry.h>
#import "RCTConversions.h"
#import "RCTEnhancedScrollView.h"
#import "RCTFabricComponentsPlugins.h"
using namespace facebook::react;
static void RCTSendPaperScrollEvent_DEPRECATED(UIScrollView *scrollView, NSInteger tag)
{
static uint16_t coalescingKey = 0;
RCTScrollEvent *scrollEvent = [[RCTScrollEvent alloc] initWithEventName:@"onScroll"
reactTag:[NSNumber numberWithInt:tag]
scrollViewContentOffset:scrollView.contentOffset
scrollViewContentInset:scrollView.contentInset
scrollViewContentSize:scrollView.contentSize
scrollViewFrame:scrollView.frame
scrollViewZoomScale:scrollView.zoomScale
userData:nil
coalescingKey:coalescingKey];
[[RCTBridge currentBridge].eventDispatcher sendEvent:scrollEvent];
}
@interface RCTScrollViewComponentView () <UIScrollViewDelegate, RCTScrollViewProtocol, RCTScrollableProtocol>
@end
@implementation RCTScrollViewComponentView {
ScrollViewShadowNode::ConcreteState::Shared _state;
CGSize _contentSize;
NSTimeInterval _lastScrollEventDispatchTime;
NSTimeInterval _scrollEventThrottle;
// Flag indicating whether the scrolling that is currently happening
// is triggered by user or not.
// This helps to only update state from `scrollViewDidScroll` in case
// some other part of the system scrolls scroll view.
BOOL _isUserTriggeredScrolling;
}
+ (RCTScrollViewComponentView *_Nullable)findScrollViewComponentViewForView:(UIView *)view
{
do {
view = view.superview;
} while (view != nil && ![view isKindOfClass:[RCTScrollViewComponentView class]]);
return (RCTScrollViewComponentView *)view;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static const auto defaultProps = std::make_shared<const ScrollViewProps>();
_props = defaultProps;
_scrollView = [[RCTEnhancedScrollView alloc] initWithFrame:self.bounds];
_scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_scrollView.delaysContentTouches = NO;
_isUserTriggeredScrolling = NO;
[self addSubview:_scrollView];
_containerView = [[UIView alloc] initWithFrame:CGRectZero];
[_scrollView addSubview:_containerView];
[self.scrollViewDelegateSplitter addDelegate:self];
_scrollEventThrottle = INFINITY;
}
return self;
}
- (void)dealloc
{
// Removing all delegates from the splitter nils the actual delegate which prevents a crash on UIScrollView
// deallocation.
[self.scrollViewDelegateSplitter removeAllDelegates];
}
- (RCTGenericDelegateSplitter<id<UIScrollViewDelegate>> *)scrollViewDelegateSplitter
{
return ((RCTEnhancedScrollView *)_scrollView).delegateSplitter;
}
#pragma mark - RCTComponentViewProtocol
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<ScrollViewComponentDescriptor>();
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
const auto &oldScrollViewProps = *std::static_pointer_cast<const ScrollViewProps>(_props);
const auto &newScrollViewProps = *std::static_pointer_cast<const ScrollViewProps>(props);
#define REMAP_PROP(reactName, localName, target) \
if (oldScrollViewProps.reactName != newScrollViewProps.reactName) { \
target.localName = newScrollViewProps.reactName; \
}
#define REMAP_VIEW_PROP(reactName, localName) REMAP_PROP(reactName, localName, self)
#define MAP_VIEW_PROP(name) REMAP_VIEW_PROP(name, name)
#define REMAP_SCROLL_VIEW_PROP(reactName, localName) \
REMAP_PROP(reactName, localName, ((RCTEnhancedScrollView *)_scrollView))
#define MAP_SCROLL_VIEW_PROP(name) REMAP_SCROLL_VIEW_PROP(name, name)
// FIXME: Commented props are not supported yet.
MAP_SCROLL_VIEW_PROP(alwaysBounceHorizontal);
MAP_SCROLL_VIEW_PROP(alwaysBounceVertical);
MAP_SCROLL_VIEW_PROP(bounces);
MAP_SCROLL_VIEW_PROP(bouncesZoom);
MAP_SCROLL_VIEW_PROP(canCancelContentTouches);
MAP_SCROLL_VIEW_PROP(centerContent);
// MAP_SCROLL_VIEW_PROP(automaticallyAdjustContentInsets);
MAP_SCROLL_VIEW_PROP(decelerationRate);
MAP_SCROLL_VIEW_PROP(directionalLockEnabled);
// MAP_SCROLL_VIEW_PROP(indicatorStyle);
// MAP_SCROLL_VIEW_PROP(keyboardDismissMode);
MAP_SCROLL_VIEW_PROP(maximumZoomScale);
MAP_SCROLL_VIEW_PROP(minimumZoomScale);
MAP_SCROLL_VIEW_PROP(scrollEnabled);
MAP_SCROLL_VIEW_PROP(pagingEnabled);
MAP_SCROLL_VIEW_PROP(pinchGestureEnabled);
MAP_SCROLL_VIEW_PROP(scrollsToTop);
MAP_SCROLL_VIEW_PROP(showsHorizontalScrollIndicator);
MAP_SCROLL_VIEW_PROP(showsVerticalScrollIndicator);
if (oldScrollViewProps.scrollEventThrottle != newScrollViewProps.scrollEventThrottle) {
// Zero means "send value only once per significant logical event".
// Prop value is in milliseconds.
// iOS implementation uses `NSTimeInterval` (in seconds).
CGFloat throttleInSeconds = newScrollViewProps.scrollEventThrottle / 1000.0;
CGFloat msPerFrame = 1.0 / 60.0;
if (throttleInSeconds < 0) {
_scrollEventThrottle = INFINITY;
} else if (throttleInSeconds <= msPerFrame) {
_scrollEventThrottle = 0;
} else {
_scrollEventThrottle = throttleInSeconds;
}
}
MAP_SCROLL_VIEW_PROP(zoomScale);
if (oldScrollViewProps.contentInset != newScrollViewProps.contentInset) {
_scrollView.contentInset = RCTUIEdgeInsetsFromEdgeInsets(newScrollViewProps.contentInset);
}
// MAP_SCROLL_VIEW_PROP(scrollIndicatorInsets);
// MAP_SCROLL_VIEW_PROP(snapToInterval);
// MAP_SCROLL_VIEW_PROP(snapToAlignment);
[super updateProps:props oldProps:oldProps];
}
- (void)updateState:(State::Shared const &)state oldState:(State::Shared const &)oldState
{
assert(std::dynamic_pointer_cast<ScrollViewShadowNode::ConcreteState const>(state));
_state = std::static_pointer_cast<ScrollViewShadowNode::ConcreteState const>(state);
CGSize contentSize = RCTCGSizeFromSize(_state->getData().getContentSize());
if (CGSizeEqualToSize(_contentSize, contentSize)) {
return;
}
_contentSize = contentSize;
_containerView.frame = CGRect{CGPointZero, contentSize};
_scrollView.contentSize = contentSize;
}
- (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
{
[_containerView insertSubview:childComponentView atIndex:index];
}
- (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
{
RCTAssert(childComponentView.superview == _containerView, @"Attempt to unmount improperly mounted component view.");
[childComponentView removeFromSuperview];
}
- (ScrollViewMetrics)_scrollViewMetrics
{
ScrollViewMetrics metrics;
metrics.contentSize = RCTSizeFromCGSize(_scrollView.contentSize);
metrics.contentOffset = RCTPointFromCGPoint(_scrollView.contentOffset);
metrics.contentInset = RCTEdgeInsetsFromUIEdgeInsets(_scrollView.contentInset);
metrics.containerSize = RCTSizeFromCGSize(_scrollView.bounds.size);
metrics.zoomScale = _scrollView.zoomScale;
return metrics;
}
- (void)_updateStateWithContentOffset
{
if (!_state) {
return;
}
auto contentOffset = RCTPointFromCGPoint(_scrollView.contentOffset);
_state->updateState([contentOffset](ScrollViewShadowNode::ConcreteState::Data const &data) {
auto newData = data;
newData.contentOffset = contentOffset;
return newData;
});
}
- (void)prepareForRecycle
{
_scrollView.contentOffset = CGPointZero;
_state.reset();
_isUserTriggeredScrolling = NO;
[super prepareForRecycle];
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (!_isUserTriggeredScrolling) {
[self _updateStateWithContentOffset];
}
if (!_eventEmitter) {
return;
}
NSTimeInterval now = CACurrentMediaTime();
if ((_lastScrollEventDispatchTime == 0) || (now - _lastScrollEventDispatchTime > _scrollEventThrottle)) {
_lastScrollEventDispatchTime = now;
std::static_pointer_cast<ScrollViewEventEmitter const>(_eventEmitter)->onScroll([self _scrollViewMetrics]);
// Once Fabric implements proper NativeAnimationDriver, this should be removed.
// This is just a workaround to allow animations based on onScroll event.
RCTSendPaperScrollEvent_DEPRECATED(scrollView, self.tag);
}
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
[self scrollViewDidScroll:scrollView];
}
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView
{
_isUserTriggeredScrolling = YES;
return YES;
}
- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView
{
_isUserTriggeredScrolling = NO;
[self _updateStateWithContentOffset];
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
[self _forceDispatchNextScrollEvent];
if (!_eventEmitter) {
return;
}
std::static_pointer_cast<ScrollViewEventEmitter const>(_eventEmitter)->onScrollBeginDrag([self _scrollViewMetrics]);
_isUserTriggeredScrolling = YES;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
[self _forceDispatchNextScrollEvent];
if (!_eventEmitter) {
return;
}
std::static_pointer_cast<ScrollViewEventEmitter const>(_eventEmitter)->onScrollEndDrag([self _scrollViewMetrics]);
[self _updateStateWithContentOffset];
}
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
[self _forceDispatchNextScrollEvent];
if (!_eventEmitter) {
return;
}
std::static_pointer_cast<ScrollViewEventEmitter const>(_eventEmitter)
->onMomentumScrollBegin([self _scrollViewMetrics]);
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self _forceDispatchNextScrollEvent];
if (!_eventEmitter) {
return;
}
std::static_pointer_cast<ScrollViewEventEmitter const>(_eventEmitter)->onMomentumScrollEnd([self _scrollViewMetrics]);
[self _updateStateWithContentOffset];
_isUserTriggeredScrolling = NO;
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
[self _forceDispatchNextScrollEvent];
if (!_eventEmitter) {
return;
}
std::static_pointer_cast<ScrollViewEventEmitter const>(_eventEmitter)->onMomentumScrollEnd([self _scrollViewMetrics]);
[self _updateStateWithContentOffset];
}
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(nullable UIView *)view
{
[self _forceDispatchNextScrollEvent];
if (!_eventEmitter) {
return;
}
std::static_pointer_cast<ScrollViewEventEmitter const>(_eventEmitter)->onScrollBeginDrag([self _scrollViewMetrics]);
}
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(nullable UIView *)view atScale:(CGFloat)scale
{
[self _forceDispatchNextScrollEvent];
if (!_eventEmitter) {
return;
}
std::static_pointer_cast<ScrollViewEventEmitter const>(_eventEmitter)->onScrollEndDrag([self _scrollViewMetrics]);
[self _updateStateWithContentOffset];
}
#pragma mark - UIScrollViewDelegate
- (void)_forceDispatchNextScrollEvent
{
_lastScrollEventDispatchTime = 0;
}
#pragma mark - Native commands
- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
{
RCTScrollViewHandleCommand(self, commandName, args);
}
- (void)flashScrollIndicators
{
[_scrollView flashScrollIndicators];
}
- (void)scrollTo:(double)x y:(double)y animated:(BOOL)animated
{
[_scrollView setContentOffset:CGPointMake(x, y) animated:animated];
}
- (void)scrollToEnd:(BOOL)animated
{
BOOL isHorizontal = _scrollView.contentSize.width > self.frame.size.width;
CGPoint offset;
if (isHorizontal) {
CGFloat offsetX = _scrollView.contentSize.width - _scrollView.bounds.size.width + _scrollView.contentInset.right;
offset = CGPointMake(fmax(offsetX, 0), 0);
} else {
CGFloat offsetY = _scrollView.contentSize.height - _scrollView.bounds.size.height + _scrollView.contentInset.bottom;
offset = CGPointMake(0, fmax(offsetY, 0));
}
[_scrollView setContentOffset:offset animated:animated];
}
#pragma mark - RCTScrollableProtocol
- (CGSize)contentSize
{
return _contentSize;
}
- (void)scrollToOffset:(CGPoint)offset
{
[self _forceDispatchNextScrollEvent];
[self scrollToOffset:offset animated:YES];
}
- (void)scrollToOffset:(CGPoint)offset animated:(BOOL)animated
{
[self _forceDispatchNextScrollEvent];
[self.scrollView setContentOffset:offset animated:animated];
}
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated
{
// Not implemented.
}
- (void)addScrollListener:(NSObject<UIScrollViewDelegate> *)scrollListener
{
[self.scrollViewDelegateSplitter addDelegate:scrollListener];
}
- (void)removeScrollListener:(NSObject<UIScrollViewDelegate> *)scrollListener
{
[self.scrollViewDelegateSplitter removeDelegate:scrollListener];
}
@end
Class<RCTComponentViewProtocol> RCTScrollViewCls(void)
{
return RCTScrollViewComponentView.class;
}

View File

@ -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 <Slider> component.
*/
@interface RCTSliderComponentView : RCTViewComponentView
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,350 @@
/*
* 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 "RCTSliderComponentView.h"
#import <React/RCTImageResponseDelegate.h>
#import <React/RCTImageResponseObserverProxy.h>
#import <react/components/rncore/EventEmitters.h>
#import <react/components/rncore/Props.h>
#import <react/components/slider/SliderComponentDescriptor.h>
#import "FBRCTFabricComponentsPlugins.h"
using namespace facebook::react;
@interface RCTSliderComponentView () <RCTImageResponseDelegate>
@end
@implementation RCTSliderComponentView {
UISlider *_sliderView;
float _previousValue;
UIImage *_trackImage;
UIImage *_minimumTrackImage;
UIImage *_maximumTrackImage;
UIImage *_thumbImage;
const ImageResponseObserverCoordinator *_trackImageCoordinator;
const ImageResponseObserverCoordinator *_minimumTrackImageCoordinator;
const ImageResponseObserverCoordinator *_maximumTrackImageCoordinator;
const ImageResponseObserverCoordinator *_thumbImageCoordinator;
RCTImageResponseObserverProxy _trackImageResponseObserverProxy;
RCTImageResponseObserverProxy _minimumTrackImageResponseObserverProxy;
RCTImageResponseObserverProxy _maximumTrackImageResponseObserverProxy;
RCTImageResponseObserverProxy _thumbImageResponseObserverProxy;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static const auto defaultProps = std::make_shared<const SliderProps>();
_props = defaultProps;
_sliderView = [[UISlider alloc] initWithFrame:self.bounds];
[_sliderView addTarget:self action:@selector(onChange:) forControlEvents:UIControlEventValueChanged];
[_sliderView addTarget:self
action:@selector(sliderTouchEnd:)
forControlEvents:(UIControlEventTouchUpInside | UIControlEventTouchUpOutside | UIControlEventTouchCancel)];
_sliderView.value = defaultProps->value;
_trackImageResponseObserverProxy = RCTImageResponseObserverProxy(self);
_minimumTrackImageResponseObserverProxy = RCTImageResponseObserverProxy(self);
_maximumTrackImageResponseObserverProxy = RCTImageResponseObserverProxy(self);
_thumbImageResponseObserverProxy = RCTImageResponseObserverProxy(self);
self.contentView = _sliderView;
}
return self;
}
// Recycling still doesn't work 100% properly
// TODO: T40099998 implement recycling properly for Fabric Slider component
- (void)prepareForRecycle
{
[super prepareForRecycle];
self.trackImageCoordinator = nullptr;
self.minimumTrackImageCoordinator = nullptr;
self.maximumTrackImageCoordinator = nullptr;
self.thumbImageCoordinator = nullptr;
// Tint colors will be taken care of when props are set again - we just
// need to make sure that image properties are reset here
[_sliderView setMinimumTrackImage:nil forState:UIControlStateNormal];
[_sliderView setMaximumTrackImage:nil forState:UIControlStateNormal];
[_sliderView setThumbImage:nil forState:UIControlStateNormal];
_trackImage = nil;
_minimumTrackImage = nil;
_maximumTrackImage = nil;
_thumbImage = nil;
}
- (void)dealloc
{
self.trackImageCoordinator = nullptr;
self.minimumTrackImageCoordinator = nullptr;
self.maximumTrackImageCoordinator = nullptr;
self.thumbImageCoordinator = nullptr;
}
#pragma mark - RCTComponentViewProtocol
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<SliderComponentDescriptor>();
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
const auto &oldSliderProps = *std::static_pointer_cast<const SliderProps>(_props);
const auto &newSliderProps = *std::static_pointer_cast<const SliderProps>(props);
// `value`
if (oldSliderProps.value != newSliderProps.value) {
_sliderView.value = newSliderProps.value;
_previousValue = newSliderProps.value;
}
// `minimumValue`
if (oldSliderProps.minimumValue != newSliderProps.minimumValue) {
_sliderView.minimumValue = newSliderProps.minimumValue;
}
// `maximumValue`
if (oldSliderProps.maximumValue != newSliderProps.maximumValue) {
_sliderView.maximumValue = newSliderProps.maximumValue;
}
// `disabled`
if (oldSliderProps.disabled != newSliderProps.disabled) {
_sliderView.enabled = !newSliderProps.disabled;
}
// `thumbTintColor`
if (oldSliderProps.thumbTintColor != newSliderProps.thumbTintColor) {
_sliderView.thumbTintColor = [UIColor colorWithCGColor:newSliderProps.thumbTintColor.get()];
}
// `minimumTrackTintColor`
if (oldSliderProps.minimumTrackTintColor != newSliderProps.minimumTrackTintColor) {
_sliderView.minimumTrackTintColor = [UIColor colorWithCGColor:newSliderProps.minimumTrackTintColor.get()];
}
// `maximumTrackTintColor`
if (oldSliderProps.maximumTrackTintColor != newSliderProps.maximumTrackTintColor) {
_sliderView.maximumTrackTintColor = [UIColor colorWithCGColor:newSliderProps.maximumTrackTintColor.get()];
}
[super updateProps:props oldProps:oldProps];
}
- (void)updateState:(facebook::react::State::Shared const &)state
oldState:(facebook::react::State::Shared const &)oldState
{
auto _state = std::static_pointer_cast<SliderShadowNode::ConcreteState const>(state);
auto _oldState = std::static_pointer_cast<SliderShadowNode::ConcreteState const>(oldState);
auto data = _state->getData();
bool havePreviousData = _oldState != nullptr;
auto getCoordinator = [](ImageRequest const *request) -> ImageResponseObserverCoordinator const * {
if (request) {
return &request->getObserverCoordinator();
} else {
return nullptr;
}
};
if (!havePreviousData || data.getTrackImageSource() != _oldState->getData().getTrackImageSource()) {
self.trackImageCoordinator = getCoordinator(&data.getTrackImageRequest());
}
if (!havePreviousData || data.getMinimumTrackImageSource() != _oldState->getData().getMinimumTrackImageSource()) {
self.minimumTrackImageCoordinator = getCoordinator(&data.getMinimumTrackImageRequest());
}
if (!havePreviousData || data.getMaximumTrackImageSource() != _oldState->getData().getMaximumTrackImageSource()) {
self.maximumTrackImageCoordinator = getCoordinator(&data.getMaximumTrackImageRequest());
}
if (!havePreviousData || data.getThumbImageSource() != _oldState->getData().getThumbImageSource()) {
self.thumbImageCoordinator = getCoordinator(&data.getThumbImageRequest());
}
}
- (void)setTrackImageCoordinator:(const ImageResponseObserverCoordinator *)coordinator
{
if (_trackImageCoordinator) {
_trackImageCoordinator->removeObserver(_trackImageResponseObserverProxy);
}
_trackImageCoordinator = coordinator;
if (_trackImageCoordinator) {
_trackImageCoordinator->addObserver(_trackImageResponseObserverProxy);
}
}
- (void)setMinimumTrackImageCoordinator:(const ImageResponseObserverCoordinator *)coordinator
{
if (_minimumTrackImageCoordinator) {
_minimumTrackImageCoordinator->removeObserver(_minimumTrackImageResponseObserverProxy);
}
_minimumTrackImageCoordinator = coordinator;
if (_minimumTrackImageCoordinator) {
_minimumTrackImageCoordinator->addObserver(_minimumTrackImageResponseObserverProxy);
}
}
- (void)setMaximumTrackImageCoordinator:(const ImageResponseObserverCoordinator *)coordinator
{
if (_maximumTrackImageCoordinator) {
_maximumTrackImageCoordinator->removeObserver(_maximumTrackImageResponseObserverProxy);
}
_maximumTrackImageCoordinator = coordinator;
if (_maximumTrackImageCoordinator) {
_maximumTrackImageCoordinator->addObserver(_maximumTrackImageResponseObserverProxy);
}
}
- (void)setThumbImageCoordinator:(const ImageResponseObserverCoordinator *)coordinator
{
if (_thumbImageCoordinator) {
_thumbImageCoordinator->removeObserver(_thumbImageResponseObserverProxy);
}
_thumbImageCoordinator = coordinator;
if (_thumbImageCoordinator) {
_thumbImageCoordinator->addObserver(_thumbImageResponseObserverProxy);
}
}
- (void)setTrackImage:(UIImage *)trackImage
{
if ([trackImage isEqual:_trackImage]) {
return;
}
_trackImage = trackImage;
_minimumTrackImage = nil;
_maximumTrackImage = nil;
CGFloat width = trackImage.size.width / 2;
UIImage *minimumTrackImage = [trackImage resizableImageWithCapInsets:(UIEdgeInsets){0, width, 0, width}
resizingMode:UIImageResizingModeStretch];
UIImage *maximumTrackImage = [trackImage resizableImageWithCapInsets:(UIEdgeInsets){0, width, 0, width}
resizingMode:UIImageResizingModeStretch];
[_sliderView setMinimumTrackImage:minimumTrackImage forState:UIControlStateNormal];
[_sliderView setMaximumTrackImage:maximumTrackImage forState:UIControlStateNormal];
}
- (void)setMinimumTrackImage:(UIImage *)minimumTrackImage
{
if ([minimumTrackImage isEqual:_minimumTrackImage] && _trackImage == nil) {
return;
}
_trackImage = nil;
_minimumTrackImage = minimumTrackImage;
_minimumTrackImage =
[_minimumTrackImage resizableImageWithCapInsets:(UIEdgeInsets){0, _minimumTrackImage.size.width, 0, 0}
resizingMode:UIImageResizingModeStretch];
[_sliderView setMinimumTrackImage:_minimumTrackImage forState:UIControlStateNormal];
}
- (void)setMaximumTrackImage:(UIImage *)maximumTrackImage
{
if ([maximumTrackImage isEqual:_maximumTrackImage] && _trackImage == nil) {
return;
}
_trackImage = nil;
_maximumTrackImage = maximumTrackImage;
_maximumTrackImage =
[_maximumTrackImage resizableImageWithCapInsets:(UIEdgeInsets){0, 0, 0, _maximumTrackImage.size.width}
resizingMode:UIImageResizingModeStretch];
[_sliderView setMaximumTrackImage:_maximumTrackImage forState:UIControlStateNormal];
}
- (void)setThumbImage:(UIImage *)thumbImage
{
if ([thumbImage isEqual:_thumbImage]) {
return;
}
_thumbImage = thumbImage;
[_sliderView setThumbImage:thumbImage forState:UIControlStateNormal];
}
- (void)onChange:(UISlider *)sender
{
[self onChange:sender withContinuous:YES];
}
- (void)sliderTouchEnd:(UISlider *)sender
{
[self onChange:sender withContinuous:NO];
}
- (void)onChange:(UISlider *)sender withContinuous:(BOOL)continuous
{
float value = sender.value;
const auto &props = *std::static_pointer_cast<const SliderProps>(_props);
if (props.step > 0 && value <= (props.maximumValue - props.minimumValue)) {
value = MAX(
props.minimumValue,
MIN(props.maximumValue, props.minimumValue + round((value - props.minimumValue) / props.step) * props.step));
[_sliderView setValue:value animated:YES];
}
if (continuous && _previousValue != value) {
std::dynamic_pointer_cast<const SliderEventEmitter>(_eventEmitter)
->onValueChange(SliderEventEmitter::OnValueChange{.value = static_cast<Float>(value)});
}
if (!continuous) {
std::dynamic_pointer_cast<const SliderEventEmitter>(_eventEmitter)
->onSlidingComplete(SliderEventEmitter::OnSlidingComplete{.value = static_cast<Float>(value)});
}
_previousValue = value;
}
#pragma mark - RCTImageResponseDelegate
- (void)didReceiveImage:(UIImage *)image fromObserver:(void const *)observer
{
if (observer == &_trackImageResponseObserverProxy) {
self.trackImage = image;
} else if (observer == &_minimumTrackImageResponseObserverProxy) {
self.minimumTrackImage = image;
} else if (observer == &_maximumTrackImageResponseObserverProxy) {
self.maximumTrackImage = image;
} else if (observer == &_thumbImageResponseObserverProxy) {
self.thumbImage = image;
}
}
- (void)didReceiveProgress:(float)progress fromObserver:(void const *)observer
{
}
- (void)didReceiveFailureFromObserver:(void const *)observer
{
}
@end
Class<RCTComponentViewProtocol> RCTSliderCls(void)
{
return RCTSliderComponentView.class;
}

View File

@ -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 <Switch> component.
*/
@interface RCTSwitchComponentView : RCTViewComponentView
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,122 @@
/*
* 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 "RCTSwitchComponentView.h"
#import <react/components/rncore/ComponentDescriptors.h>
#import <react/components/rncore/EventEmitters.h>
#import <react/components/rncore/Props.h>
#import <react/components/rncore/RCTComponentViewHelpers.h>
#import "FBRCTFabricComponentsPlugins.h"
using namespace facebook::react;
@interface RCTSwitchComponentView () <RCTSwitchViewProtocol>
@end
@implementation RCTSwitchComponentView {
UISwitch *_switchView;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
_switchView = [[UISwitch alloc] initWithFrame:self.bounds];
[_switchView addTarget:self action:@selector(onChange:) forControlEvents:UIControlEventValueChanged];
self.contentView = _switchView;
[self setPropsToDefault];
}
return self;
}
- (void)setPropsToDefault
{
static const auto defaultProps = std::make_shared<const SwitchProps>();
_props = defaultProps;
_switchView.on = defaultProps->value;
}
#pragma mark - RCTComponentViewProtocol
- (void)prepareForRecycle
{
[super prepareForRecycle];
[self setPropsToDefault];
}
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<SwitchComponentDescriptor>();
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
const auto &oldSwitchProps = *std::static_pointer_cast<const SwitchProps>(_props);
const auto &newSwitchProps = *std::static_pointer_cast<const SwitchProps>(props);
// `value`
if (oldSwitchProps.value != newSwitchProps.value) {
_switchView.on = newSwitchProps.value;
}
// `disabled`
if (oldSwitchProps.disabled != newSwitchProps.disabled) {
_switchView.enabled = !newSwitchProps.disabled;
}
// `tintColor`
if (oldSwitchProps.tintColor != newSwitchProps.tintColor) {
_switchView.tintColor = [UIColor colorWithCGColor:newSwitchProps.tintColor.get()];
}
// `onTintColor
if (oldSwitchProps.onTintColor != newSwitchProps.onTintColor) {
_switchView.onTintColor = [UIColor colorWithCGColor:newSwitchProps.onTintColor.get()];
}
// `thumbTintColor`
if (oldSwitchProps.thumbTintColor != newSwitchProps.thumbTintColor) {
_switchView.thumbTintColor = [UIColor colorWithCGColor:newSwitchProps.thumbTintColor.get()];
}
[super updateProps:props oldProps:oldProps];
}
- (void)onChange:(UISwitch *)sender
{
const auto &props = *std::static_pointer_cast<const SwitchProps>(_props);
if (props.value == sender.on) {
return;
}
std::dynamic_pointer_cast<const SwitchEventEmitter>(_eventEmitter)
->onChange(SwitchEventEmitter::OnChange{.value = static_cast<bool>(sender.on)});
}
#pragma mark - Native Commands
- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
{
RCTSwitchHandleCommand(self, commandName, args);
}
- (void)setValue:(BOOL)value
{
[_switchView setOn:value animated:YES];
}
@end
Class<RCTComponentViewProtocol> RCTSwitchCls(void)
{
return RCTSwitchComponentView.class;
}

View File

@ -0,0 +1,27 @@
/*
* 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 <Paragraph> component.
*/
@interface RCTParagraphComponentView : RCTViewComponentView
/*
* Returns an `NSAttributedString` representing the content of the component.
* To be only used by external introspection and debug tools.
*/
@property (nonatomic, nullable, readonly) NSAttributedString *attributedText;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,171 @@
/*
* 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 "RCTParagraphComponentView.h"
#import <react/components/text/ParagraphComponentDescriptor.h>
#import <react/components/text/ParagraphProps.h>
#import <react/components/text/ParagraphState.h>
#import <react/components/text/RawTextComponentDescriptor.h>
#import <react/components/text/TextComponentDescriptor.h>
#import <react/graphics/Geometry.h>
#import <react/textlayoutmanager/RCTAttributedTextUtils.h>
#import <react/textlayoutmanager/RCTTextLayoutManager.h>
#import <react/textlayoutmanager/TextLayoutManager.h>
#import <react/utils/ManagedObjectWrapper.h>
#import "RCTConversions.h"
using namespace facebook::react;
@implementation RCTParagraphComponentView {
ParagraphShadowNode::ConcreteState::Shared _state;
ParagraphAttributes _paragraphAttributes;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static const auto defaultProps = std::make_shared<const ParagraphProps>();
_props = defaultProps;
self.isAccessibilityElement = YES;
self.accessibilityTraits |= UIAccessibilityTraitStaticText;
self.opaque = NO;
self.contentMode = UIViewContentModeRedraw;
}
return self;
}
- (NSString *)description
{
NSString *superDescription = [super description];
// Cutting the last `>` character.
if (superDescription.length > 0 && [superDescription characterAtIndex:superDescription.length - 1] == '>') {
superDescription = [superDescription substringToIndex:superDescription.length - 1];
}
return [NSString stringWithFormat:@"%@; attributedText = %@>", superDescription, self.attributedText];
}
- (NSAttributedString *_Nullable)attributedText
{
if (!_state) {
return nil;
}
return RCTNSAttributedStringFromAttributedString(_state->getData().attributedString);
}
#pragma mark - RCTComponentViewProtocol
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<ParagraphComponentDescriptor>();
}
+ (std::vector<facebook::react::ComponentDescriptorProvider>)supplementalComponentDescriptorProviders
{
return {concreteComponentDescriptorProvider<RawTextComponentDescriptor>(),
concreteComponentDescriptorProvider<TextComponentDescriptor>()};
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
const auto &paragraphProps = std::static_pointer_cast<const ParagraphProps>(props);
assert(paragraphProps);
_paragraphAttributes = paragraphProps->paragraphAttributes;
[super updateProps:props oldProps:oldProps];
}
- (void)updateState:(State::Shared const &)state oldState:(State::Shared const &)oldState
{
_state = std::static_pointer_cast<ParagraphShadowNode::ConcreteState const>(state);
[self setNeedsDisplay];
}
- (void)prepareForRecycle
{
[super prepareForRecycle];
_state.reset();
}
- (void)drawRect:(CGRect)rect
{
if (!_state) {
return;
}
auto textLayoutManager = _state->getData().layoutManager;
assert(textLayoutManager && "TextLayoutManager must not be `nullptr`.");
if (!textLayoutManager) {
return;
}
RCTTextLayoutManager *nativeTextLayoutManager =
(RCTTextLayoutManager *)unwrapManagedObject(textLayoutManager->getNativeTextLayoutManager());
CGRect frame = RCTCGRectFromRect(_layoutMetrics.getContentFrame());
[nativeTextLayoutManager drawAttributedString:_state->getData().attributedString
paragraphAttributes:_paragraphAttributes
frame:frame];
}
#pragma mark - Accessibility
- (NSString *)accessibilityLabel
{
NSString *superAccessibilityLabel = RCTNSStringFromStringNilIfEmpty(_props->accessibilityLabel);
if (superAccessibilityLabel) {
return superAccessibilityLabel;
}
if (!_state) {
return nil;
}
return RCTNSStringFromString(_state->getData().attributedString.getString());
}
- (SharedTouchEventEmitter)touchEventEmitterAtPoint:(CGPoint)point
{
if (!_state) {
return _eventEmitter;
}
auto textLayoutManager = _state->getData().layoutManager;
assert(textLayoutManager && "TextLayoutManager must not be `nullptr`.");
if (!textLayoutManager) {
return _eventEmitter;
}
RCTTextLayoutManager *nativeTextLayoutManager =
(RCTTextLayoutManager *)unwrapManagedObject(textLayoutManager->getNativeTextLayoutManager());
CGRect frame = RCTCGRectFromRect(_layoutMetrics.getContentFrame());
auto eventEmitter = [nativeTextLayoutManager getEventEmitterWithAttributeString:_state->getData().attributedString
paragraphAttributes:_paragraphAttributes
frame:frame
atPoint:point];
if (!eventEmitter) {
return _eventEmitter;
}
assert(std::dynamic_pointer_cast<const TouchEventEmitter>(eventEmitter));
return std::static_pointer_cast<const TouchEventEmitter>(eventEmitter);
}
@end

View File

@ -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 <TextInput> component.
*/
@interface RCTTextInputComponentView : RCTViewComponentView
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,439 @@
/*
* 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 "RCTTextInputComponentView.h"
#import <react/components/iostextinput/TextInputComponentDescriptor.h>
#import <react/graphics/Geometry.h>
#import <react/textlayoutmanager/RCTAttributedTextUtils.h>
#import <react/textlayoutmanager/TextLayoutManager.h>
#import <React/RCTBackedTextInputViewProtocol.h>
#import <React/RCTUITextField.h>
#import <React/RCTUITextView.h>
#import "RCTConversions.h"
#import "RCTTextInputNativeCommands.h"
#import "RCTTextInputUtils.h"
using namespace facebook::react;
@interface RCTTextInputComponentView () <RCTBackedTextInputDelegate, RCTTextInputViewProtocol>
@end
@implementation RCTTextInputComponentView {
TextInputShadowNode::ConcreteState::Shared _state;
UIView<RCTBackedTextInputViewProtocol> *_backedTextInputView;
NSUInteger _mostRecentEventCount;
NSAttributedString *_lastStringStateWasUpdatedWith;
/*
* UIKit uses either UITextField or UITextView as its UIKit element for <TextInput>. UITextField is for single line
* entry, UITextView is for multiline entry. There is a problem with order of events when user types a character. In
* UITextField (single line text entry), typing a character first triggers `onChange` event and then
* onSelectionChange. In UITextView (multi line text entry), typing a character first triggers `onSelectionChange` and
* then onChange. JavaScript depends on `onChange` to be called before `onSelectionChange`. This flag keeps state so
* if UITextView is backing text input view, inside `-[RCTTextInputComponentView textInputDidChangeSelection]` we make
* sure to call `onChange` before `onSelectionChange` and ignore next `-[RCTTextInputComponentView
* textInputDidChange]` call.
*/
BOOL _ignoreNextTextInputCall;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static const auto defaultProps = std::make_shared<TextInputProps const>();
_props = defaultProps;
auto &props = *defaultProps;
_backedTextInputView = props.traits.multiline ? [[RCTUITextView alloc] init] : [[RCTUITextField alloc] init];
_backedTextInputView.frame = self.bounds;
_backedTextInputView.textInputDelegate = self;
_ignoreNextTextInputCall = NO;
[self addSubview:_backedTextInputView];
}
return self;
}
#pragma mark - RCTComponentViewProtocol
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<TextInputComponentDescriptor>();
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
auto const &oldTextInputProps = *std::static_pointer_cast<TextInputProps const>(_props);
auto const &newTextInputProps = *std::static_pointer_cast<TextInputProps const>(props);
// Traits:
if (newTextInputProps.traits.multiline != oldTextInputProps.traits.multiline) {
[self _setMultiline:newTextInputProps.traits.multiline];
}
if (newTextInputProps.traits.autocapitalizationType != oldTextInputProps.traits.autocapitalizationType) {
_backedTextInputView.autocapitalizationType =
RCTUITextAutocapitalizationTypeFromAutocapitalizationType(newTextInputProps.traits.autocapitalizationType);
}
if (newTextInputProps.traits.autoCorrect != oldTextInputProps.traits.autoCorrect) {
_backedTextInputView.autocorrectionType =
RCTUITextAutocorrectionTypeFromOptionalBool(newTextInputProps.traits.autoCorrect);
}
if (newTextInputProps.traits.contextMenuHidden != oldTextInputProps.traits.contextMenuHidden) {
_backedTextInputView.contextMenuHidden = newTextInputProps.traits.contextMenuHidden;
}
if (newTextInputProps.traits.editable != oldTextInputProps.traits.editable) {
_backedTextInputView.editable = newTextInputProps.traits.editable;
}
if (newTextInputProps.traits.enablesReturnKeyAutomatically !=
oldTextInputProps.traits.enablesReturnKeyAutomatically) {
_backedTextInputView.enablesReturnKeyAutomatically = newTextInputProps.traits.enablesReturnKeyAutomatically;
}
if (newTextInputProps.traits.keyboardAppearance != oldTextInputProps.traits.keyboardAppearance) {
_backedTextInputView.keyboardAppearance =
RCTUIKeyboardAppearanceFromKeyboardAppearance(newTextInputProps.traits.keyboardAppearance);
}
if (newTextInputProps.traits.spellCheck != oldTextInputProps.traits.spellCheck) {
_backedTextInputView.spellCheckingType =
RCTUITextSpellCheckingTypeFromOptionalBool(newTextInputProps.traits.spellCheck);
}
if (newTextInputProps.traits.caretHidden != oldTextInputProps.traits.caretHidden) {
_backedTextInputView.caretHidden = newTextInputProps.traits.caretHidden;
}
if (newTextInputProps.traits.clearButtonMode != oldTextInputProps.traits.clearButtonMode) {
_backedTextInputView.clearButtonMode =
RCTUITextFieldViewModeFromTextInputAccessoryVisibilityMode(newTextInputProps.traits.clearButtonMode);
}
if (newTextInputProps.traits.scrollEnabled != oldTextInputProps.traits.scrollEnabled) {
_backedTextInputView.scrollEnabled = newTextInputProps.traits.scrollEnabled;
}
if (newTextInputProps.traits.secureTextEntry != oldTextInputProps.traits.secureTextEntry) {
_backedTextInputView.secureTextEntry = newTextInputProps.traits.secureTextEntry;
}
if (newTextInputProps.traits.keyboardType != oldTextInputProps.traits.keyboardType) {
_backedTextInputView.keyboardType = RCTUIKeyboardTypeFromKeyboardType(newTextInputProps.traits.keyboardType);
}
if (newTextInputProps.traits.returnKeyType != oldTextInputProps.traits.returnKeyType) {
_backedTextInputView.returnKeyType = RCTUIReturnKeyTypeFromReturnKeyType(newTextInputProps.traits.returnKeyType);
}
if (newTextInputProps.traits.textContentType != oldTextInputProps.traits.textContentType) {
if (@available(iOS 10.0, *)) {
_backedTextInputView.textContentType = RCTUITextContentTypeFromString(newTextInputProps.traits.textContentType);
}
}
if (newTextInputProps.traits.passwordRules != oldTextInputProps.traits.passwordRules) {
if (@available(iOS 12.0, *)) {
_backedTextInputView.passwordRules =
RCTUITextInputPasswordRulesFromString(newTextInputProps.traits.passwordRules);
}
}
// Traits `blurOnSubmit`, `clearTextOnFocus`, and `selectTextOnFocus` were omitted intentially here
// because they are being checked on-demand.
// Other props:
if (newTextInputProps.placeholder != oldTextInputProps.placeholder) {
_backedTextInputView.placeholder = RCTNSStringFromString(newTextInputProps.placeholder);
}
if (newTextInputProps.textAttributes != oldTextInputProps.textAttributes) {
_backedTextInputView.defaultTextAttributes =
RCTNSTextAttributesFromTextAttributes(newTextInputProps.getEffectiveTextAttributes());
}
if (newTextInputProps.selectionColor != oldTextInputProps.selectionColor) {
_backedTextInputView.tintColor = RCTUIColorFromSharedColor(newTextInputProps.selectionColor);
}
[super updateProps:props oldProps:oldProps];
}
- (void)updateState:(State::Shared const &)state oldState:(State::Shared const &)oldState
{
_state = std::static_pointer_cast<TextInputShadowNode::ConcreteState const>(state);
if (!_state) {
assert(false && "State is `null` for <TextInput> component.");
_backedTextInputView.attributedText = nil;
return;
}
if (_mostRecentEventCount == _state->getData().mostRecentEventCount) {
auto data = _state->getData();
[self _setAttributedString:RCTNSAttributedStringFromAttributedStringBox(data.attributedStringBox)];
}
}
- (void)updateLayoutMetrics:(LayoutMetrics const &)layoutMetrics
oldLayoutMetrics:(LayoutMetrics const &)oldLayoutMetrics
{
[super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
_backedTextInputView.frame =
UIEdgeInsetsInsetRect(self.bounds, RCTUIEdgeInsetsFromEdgeInsets(layoutMetrics.borderWidth));
_backedTextInputView.textContainerInset =
RCTUIEdgeInsetsFromEdgeInsets(layoutMetrics.contentInsets - layoutMetrics.borderWidth);
}
- (void)_setAttributedString:(NSAttributedString *)attributedString
{
UITextRange *selectedRange = [_backedTextInputView selectedTextRange];
_backedTextInputView.attributedText = attributedString;
// Calling `[_backedTextInputView setAttributedText]` results
// in `textInputDidChangeSelection` being called but not `textInputDidChange`.
// For `_ignoreNextTextInputCall` to have correct value, these calls
// need to be balanced, that's why we manually set the flag here.
_ignoreNextTextInputCall = NO;
if (_lastStringStateWasUpdatedWith.length == attributedString.length) {
// Calling `[_backedTextInputView setAttributedText]` moves caret
// to the end of text input field. This cancels any selection as well
// as position in the text input field. In case the length of string
// doesn't change, selection and caret position is maintained.
[_backedTextInputView setSelectedTextRange:selectedRange notifyDelegate:NO];
}
_lastStringStateWasUpdatedWith = attributedString;
}
- (void)prepareForRecycle
{
[super prepareForRecycle];
_backedTextInputView.attributedText = [[NSAttributedString alloc] init];
_mostRecentEventCount = 0;
_state.reset();
_lastStringStateWasUpdatedWith = nil;
_ignoreNextTextInputCall = NO;
}
#pragma mark - RCTComponentViewProtocol
- (void)_setMultiline:(BOOL)multiline
{
[_backedTextInputView removeFromSuperview];
UIView<RCTBackedTextInputViewProtocol> *backedTextInputView =
multiline ? [[RCTUITextView alloc] init] : [[RCTUITextField alloc] init];
backedTextInputView.frame = _backedTextInputView.frame;
RCTCopyBackedTextInput(_backedTextInputView, backedTextInputView);
_backedTextInputView = backedTextInputView;
[self addSubview:_backedTextInputView];
}
#pragma mark - RCTBackedTextInputDelegate
- (BOOL)textInputShouldBeginEditing
{
return YES;
}
- (void)textInputDidBeginEditing
{
auto const &props = *std::static_pointer_cast<TextInputProps const>(_props);
if (props.traits.clearTextOnFocus) {
_backedTextInputView.attributedText = [NSAttributedString new];
[self textInputDidChange];
}
if (props.traits.selectTextOnFocus) {
[_backedTextInputView selectAll:nil];
[self textInputDidChangeSelection];
}
if (_eventEmitter) {
std::static_pointer_cast<TextInputEventEmitter const>(_eventEmitter)->onFocus([self _textInputMetrics]);
}
}
- (BOOL)textInputShouldEndEditing
{
return YES;
}
- (void)textInputDidEndEditing
{
if (_eventEmitter) {
std::static_pointer_cast<TextInputEventEmitter const>(_eventEmitter)->onEndEditing([self _textInputMetrics]);
std::static_pointer_cast<TextInputEventEmitter const>(_eventEmitter)->onBlur([self _textInputMetrics]);
}
}
- (BOOL)textInputShouldReturn
{
// We send `submit` event here, in `textInputShouldReturn`
// (not in `textInputDidReturn)`, because of semantic of the event:
// `onSubmitEditing` is called when "Submit" button
// (the blue key on onscreen keyboard) did pressed
// (no connection to any specific "submitting" process).
if (_eventEmitter) {
std::static_pointer_cast<TextInputEventEmitter const>(_eventEmitter)->onSubmitEditing([self _textInputMetrics]);
}
auto const &props = *std::static_pointer_cast<TextInputProps const>(_props);
return props.traits.blurOnSubmit;
}
- (void)textInputDidReturn
{
// Does nothing.
}
- (NSString *)textInputShouldChangeText:(NSString *)text inRange:(NSRange)range
{
if (!_backedTextInputView.textWasPasted) {
if (_eventEmitter) {
KeyPressMetrics keyPressMetrics;
keyPressMetrics.text = RCTStringFromNSString(text);
keyPressMetrics.eventCount = _mostRecentEventCount;
std::static_pointer_cast<TextInputEventEmitter const>(_eventEmitter)->onKeyPress(keyPressMetrics);
}
}
auto const &props = *std::static_pointer_cast<TextInputProps const>(_props);
if (props.maxLength) {
NSInteger allowedLength = props.maxLength - _backedTextInputView.attributedText.string.length + range.length;
if (allowedLength <= 0) {
return nil;
}
return allowedLength > text.length ? text : [text substringToIndex:allowedLength];
}
return text;
}
- (BOOL)textInputShouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
return YES;
}
- (void)textInputDidChange
{
if (_ignoreNextTextInputCall) {
_ignoreNextTextInputCall = NO;
return;
}
[self _updateState];
if (_eventEmitter) {
std::static_pointer_cast<TextInputEventEmitter const>(_eventEmitter)->onChange([self _textInputMetrics]);
}
}
- (void)textInputDidChangeSelection
{
auto const &props = *std::static_pointer_cast<TextInputProps const>(_props);
if (props.traits.multiline && ![_lastStringStateWasUpdatedWith isEqual:_backedTextInputView.attributedText]) {
[self textInputDidChange];
_ignoreNextTextInputCall = YES;
}
if (_eventEmitter) {
std::static_pointer_cast<TextInputEventEmitter const>(_eventEmitter)->onSelectionChange([self _textInputMetrics]);
}
}
#pragma mark - Other
- (TextInputMetrics)_textInputMetrics
{
TextInputMetrics metrics;
metrics.text = RCTStringFromNSString(_backedTextInputView.attributedText.string);
metrics.selectionRange = [self _selectionRange];
metrics.eventCount = _mostRecentEventCount;
return metrics;
}
- (void)_updateState
{
NSAttributedString *attributedString = _backedTextInputView.attributedText;
if (!_state) {
return;
}
auto data = _state->getData();
_lastStringStateWasUpdatedWith = attributedString;
data.attributedStringBox = RCTAttributedStringBoxFromNSAttributedString(attributedString);
_mostRecentEventCount += 1;
data.mostRecentEventCount = _mostRecentEventCount;
_state->updateState(std::move(data));
}
- (AttributedString::Range)_selectionRange
{
UITextRange *selectedTextRange = _backedTextInputView.selectedTextRange;
NSInteger start = [_backedTextInputView offsetFromPosition:_backedTextInputView.beginningOfDocument
toPosition:selectedTextRange.start];
NSInteger end = [_backedTextInputView offsetFromPosition:_backedTextInputView.beginningOfDocument
toPosition:selectedTextRange.end];
return AttributedString::Range{(int)start, (int)(end - start)};
}
#pragma mark - Native Commands
- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
{
RCTTextInputHandleCommand(self, commandName, args);
}
- (void)focus
{
[_backedTextInputView becomeFirstResponder];
}
- (void)blur
{
[_backedTextInputView resignFirstResponder];
}
- (void)setTextAndSelection:(NSInteger)eventCount
value:(NSString *__nullable)value
start:(NSInteger)start
end:(NSInteger)end
{
if (_mostRecentEventCount != eventCount) {
return;
}
if (value) {
NSMutableAttributedString *mutableString =
[[NSMutableAttributedString alloc] initWithAttributedString:_backedTextInputView.attributedText];
[mutableString replaceCharactersInRange:NSMakeRange(0, _backedTextInputView.attributedText.length)
withString:value];
[self _setAttributedString:mutableString];
[self _updateState];
}
UITextPosition *startPosition = [_backedTextInputView positionFromPosition:_backedTextInputView.beginningOfDocument
offset:start];
UITextPosition *endPosition = [_backedTextInputView positionFromPosition:_backedTextInputView.beginningOfDocument
offset:end];
if (startPosition && endPosition) {
UITextRange *range = [_backedTextInputView textRangeFromPosition:startPosition toPosition:endPosition];
[_backedTextInputView setSelectedTextRange:range notifyDelegate:NO];
}
}
@end

View File

@ -0,0 +1,104 @@
/*
* 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 <Foundation/Foundation.h>
#import <React/RCTDefines.h>
#import <React/RCTLog.h>
NS_ASSUME_NONNULL_BEGIN
@protocol RCTTextInputViewProtocol <NSObject>
- (void)focus;
- (void)blur;
- (void)setTextAndSelection:(NSInteger)eventCount
value:(NSString *__nullable)value
start:(NSInteger)start
end:(NSInteger)end;
@end
RCT_EXTERN inline void
RCTTextInputHandleCommand(id<RCTTextInputViewProtocol> componentView, NSString const *commandName, NSArray const *args)
{
if ([commandName isEqualToString:@"focus"]) {
#if RCT_DEBUG
if ([args count] != 0) {
RCTLogError(
@"%@ command %@ received %d arguments, expected %d.", @"TextInput", commandName, (int)[args count], 0);
return;
}
#endif
[componentView focus];
return;
}
if ([commandName isEqualToString:@"blur"]) {
#if RCT_DEBUG
if ([args count] != 0) {
RCTLogError(
@"%@ command %@ received %d arguments, expected %d.", @"TextInput", commandName, (int)[args count], 0);
return;
}
#endif
[componentView blur];
return;
}
if ([commandName isEqualToString:@"setTextAndSelection"]) {
#if RCT_DEBUG
if ([args count] != 4) {
RCTLogError(
@"%@ command %@ received %d arguments, expected %d.", @"TextInput", commandName, (int)[args count], 4);
return;
}
#endif
NSObject *arg0 = args[0];
#if RCT_DEBUG
if (!RCTValidateTypeOfViewCommandArgument(arg0, [NSNumber class], @"number", @"TextInput", commandName, @"1st")) {
return;
}
#endif
NSInteger eventCount = [(NSNumber *)arg0 intValue];
NSObject *arg1 = args[1];
#if RCT_DEBUG
if (![arg1 isKindOfClass:[NSNull class]] &&
!RCTValidateTypeOfViewCommandArgument(arg1, [NSString class], @"string", @"TextInput", commandName, @"2nd")) {
return;
}
#endif
NSString *value = [arg1 isKindOfClass:[NSNull class]] ? nil : (NSString *)arg1;
NSObject *arg2 = args[2];
#if RCT_DEBUG
if (!RCTValidateTypeOfViewCommandArgument(arg2, [NSNumber class], @"number", @"TextInput", commandName, @"3rd")) {
return;
}
#endif
NSInteger start = [(NSNumber *)arg2 intValue];
NSObject *arg3 = args[3];
#if RCT_DEBUG
if (!RCTValidateTypeOfViewCommandArgument(arg3, [NSNumber class], @"number", @"TextInput", commandName, @"4th")) {
return;
}
#endif
NSInteger end = [(NSNumber *)arg3 intValue];
[componentView setTextAndSelection:eventCount value:value start:start end:end];
return;
}
#if RCT_DEBUG
RCTLogError(@"%@ received command %@, which is not a supported command.", @"TextInput", commandName);
#endif
}
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,43 @@
/*
* 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/RCTBackedTextInputViewProtocol.h>
#import <better/optional.h>
#import <react/components/iostextinput/primitives.h>
NS_ASSUME_NONNULL_BEGIN
void RCTCopyBackedTextInput(
UIView<RCTBackedTextInputViewProtocol> *fromTextInput,
UIView<RCTBackedTextInputViewProtocol> *toTextInput);
UITextAutocorrectionType RCTUITextAutocorrectionTypeFromOptionalBool(facebook::better::optional<bool> autoCorrect);
UITextAutocapitalizationType RCTUITextAutocapitalizationTypeFromAutocapitalizationType(
facebook::react::AutocapitalizationType autocapitalizationType);
UIKeyboardAppearance RCTUIKeyboardAppearanceFromKeyboardAppearance(
facebook::react::KeyboardAppearance keyboardAppearance);
UITextSpellCheckingType RCTUITextSpellCheckingTypeFromOptionalBool(facebook::better::optional<bool> spellCheck);
UITextFieldViewMode RCTUITextFieldViewModeFromTextInputAccessoryVisibilityMode(
facebook::react::TextInputAccessoryVisibilityMode mode);
UIKeyboardType RCTUIKeyboardTypeFromKeyboardType(facebook::react::KeyboardType keyboardType);
UIReturnKeyType RCTUIReturnKeyTypeFromReturnKeyType(facebook::react::ReturnKeyType returnKeyType);
API_AVAILABLE(ios(10.0))
UITextContentType RCTUITextContentTypeFromString(std::string const &contentType);
API_AVAILABLE(ios(12.0))
UITextInputPasswordRules *RCTUITextInputPasswordRulesFromString(std::string const &passwordRules);
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,198 @@
/*
* 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 "RCTTextInputUtils.h"
#import <React/RCTConversions.h>
using namespace facebook::react;
static NSAttributedString *RCTSanitizeAttributedString(NSAttributedString *attributedString)
{
// Here we need to remove text attributes specific to particular kind of TextInput on iOS (e.g. limiting line number).
// TODO: Implement it properly.
return [[NSAttributedString alloc] initWithString:attributedString.string];
}
void RCTCopyBackedTextInput(
UIView<RCTBackedTextInputViewProtocol> *fromTextInput,
UIView<RCTBackedTextInputViewProtocol> *toTextInput)
{
toTextInput.attributedText = RCTSanitizeAttributedString(fromTextInput.attributedText);
toTextInput.placeholder = fromTextInput.placeholder;
toTextInput.placeholderColor = fromTextInput.placeholderColor;
toTextInput.textContainerInset = fromTextInput.textContainerInset;
toTextInput.inputAccessoryView = fromTextInput.inputAccessoryView;
toTextInput.textInputDelegate = fromTextInput.textInputDelegate;
toTextInput.placeholderColor = fromTextInput.placeholderColor;
toTextInput.defaultTextAttributes = fromTextInput.defaultTextAttributes;
toTextInput.autocapitalizationType = fromTextInput.autocapitalizationType;
toTextInput.autocorrectionType = fromTextInput.autocorrectionType;
toTextInput.contextMenuHidden = fromTextInput.contextMenuHidden;
toTextInput.editable = fromTextInput.editable;
toTextInput.enablesReturnKeyAutomatically = fromTextInput.enablesReturnKeyAutomatically;
toTextInput.keyboardAppearance = fromTextInput.keyboardAppearance;
toTextInput.spellCheckingType = fromTextInput.spellCheckingType;
toTextInput.caretHidden = fromTextInput.caretHidden;
toTextInput.clearButtonMode = fromTextInput.clearButtonMode;
toTextInput.scrollEnabled = fromTextInput.scrollEnabled;
toTextInput.secureTextEntry = fromTextInput.secureTextEntry;
toTextInput.keyboardType = fromTextInput.keyboardType;
if (@available(iOS 10.0, *)) {
toTextInput.textContentType = fromTextInput.textContentType;
}
if (@available(iOS 12.0, *)) {
toTextInput.passwordRules = fromTextInput.passwordRules;
}
[toTextInput setSelectedTextRange:fromTextInput.selectedTextRange notifyDelegate:NO];
}
UITextAutocorrectionType RCTUITextAutocorrectionTypeFromOptionalBool(facebook::better::optional<bool> autoCorrect)
{
return autoCorrect.has_value() ? (*autoCorrect ? UITextAutocorrectionTypeYes : UITextAutocorrectionTypeNo)
: UITextAutocorrectionTypeDefault;
}
UITextAutocapitalizationType RCTUITextAutocapitalizationTypeFromAutocapitalizationType(
AutocapitalizationType autocapitalizationType)
{
switch (autocapitalizationType) {
case AutocapitalizationType::None:
return UITextAutocapitalizationTypeNone;
case AutocapitalizationType::Words:
return UITextAutocapitalizationTypeWords;
case AutocapitalizationType::Sentences:
return UITextAutocapitalizationTypeSentences;
case AutocapitalizationType::Characters:
return UITextAutocapitalizationTypeAllCharacters;
}
}
UIKeyboardAppearance RCTUIKeyboardAppearanceFromKeyboardAppearance(KeyboardAppearance keyboardAppearance)
{
switch (keyboardAppearance) {
case KeyboardAppearance::Default:
return UIKeyboardAppearanceDefault;
case KeyboardAppearance::Light:
return UIKeyboardAppearanceLight;
case KeyboardAppearance::Dark:
return UIKeyboardAppearanceDark;
}
}
UITextSpellCheckingType RCTUITextSpellCheckingTypeFromOptionalBool(facebook::better::optional<bool> spellCheck)
{
return spellCheck.has_value() ? (*spellCheck ? UITextSpellCheckingTypeYes : UITextSpellCheckingTypeNo)
: UITextSpellCheckingTypeDefault;
}
UITextFieldViewMode RCTUITextFieldViewModeFromTextInputAccessoryVisibilityMode(
facebook::react::TextInputAccessoryVisibilityMode mode)
{
switch (mode) {
case TextInputAccessoryVisibilityMode::Never:
return UITextFieldViewModeNever;
case TextInputAccessoryVisibilityMode::WhileEditing:
return UITextFieldViewModeWhileEditing;
case TextInputAccessoryVisibilityMode::UnlessEditing:
return UITextFieldViewModeUnlessEditing;
case TextInputAccessoryVisibilityMode::Always:
return UITextFieldViewModeAlways;
}
}
UIKeyboardType RCTUIKeyboardTypeFromKeyboardType(KeyboardType keyboardType)
{
switch (keyboardType) {
// Universal
case KeyboardType::Default:
return UIKeyboardTypeDefault;
case KeyboardType::EmailAddress:
return UIKeyboardTypeEmailAddress;
case KeyboardType::Numeric:
return UIKeyboardTypeDecimalPad;
case KeyboardType::PhonePad:
return UIKeyboardTypePhonePad;
case KeyboardType::NumberPad:
return UIKeyboardTypeNumberPad;
case KeyboardType::DecimalPad:
return UIKeyboardTypeDecimalPad;
// iOS-only
case KeyboardType::ASCIICapable:
return UIKeyboardTypeASCIICapable;
case KeyboardType::NumbersAndPunctuation:
return UIKeyboardTypeNumbersAndPunctuation;
case KeyboardType::URL:
return UIKeyboardTypeURL;
case KeyboardType::NamePhonePad:
return UIKeyboardTypeNamePhonePad;
case KeyboardType::Twitter:
return UIKeyboardTypeTwitter;
case KeyboardType::WebSearch:
return UIKeyboardTypeWebSearch;
case KeyboardType::ASCIICapableNumberPad:
if (@available(iOS 10.0, *)) {
return UIKeyboardTypeASCIICapableNumberPad;
} else {
return UIKeyboardTypeNumberPad;
}
// Android-only
case KeyboardType::VisiblePassword:
return UIKeyboardTypeDefault;
}
}
UIReturnKeyType RCTUIReturnKeyTypeFromReturnKeyType(ReturnKeyType returnKeyType)
{
switch (returnKeyType) {
case ReturnKeyType::Default:
return UIReturnKeyDefault;
case ReturnKeyType::Done:
return UIReturnKeyDone;
case ReturnKeyType::Go:
return UIReturnKeyGo;
case ReturnKeyType::Next:
return UIReturnKeyNext;
case ReturnKeyType::Search:
return UIReturnKeySearch;
case ReturnKeyType::Send:
return UIReturnKeySend;
// iOS-only
case ReturnKeyType::EmergencyCall:
return UIReturnKeyEmergencyCall;
case ReturnKeyType::Google:
return UIReturnKeyGoogle;
case ReturnKeyType::Join:
return UIReturnKeyJoin;
case ReturnKeyType::Route:
return UIReturnKeyRoute;
case ReturnKeyType::Yahoo:
return UIReturnKeyYahoo;
case ReturnKeyType::Continue:
return UIReturnKeyContinue;
// Android-only
case ReturnKeyType::None:
case ReturnKeyType::Previous:
return UIReturnKeyDefault;
}
}
API_AVAILABLE(ios(10.0))
UITextContentType RCTUITextContentTypeFromString(std::string const &contentType)
{
// TODO: Implement properly (T26519801).
return RCTNSStringFromStringNilIfEmpty(contentType);
}
API_AVAILABLE(ios(12.0))
UITextInputPasswordRules *RCTUITextInputPasswordRulesFromString(std::string const &passwordRules)
{
return [UITextInputPasswordRules passwordRulesWithDescriptor:RCTNSStringFromStringNilIfEmpty(passwordRules)];
}

View File

@ -0,0 +1,18 @@
/*
* 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
@interface RCTUnimplementedNativeComponentView : RCTViewComponentView
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,60 @@
/*
* 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 "RCTUnimplementedNativeComponentView.h"
#import <react/components/rncore/ComponentDescriptors.h>
#import <react/components/rncore/EventEmitters.h>
#import <react/components/rncore/Props.h>
using namespace facebook::react;
@implementation RCTUnimplementedNativeComponentView {
UILabel *_label;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static const auto defaultProps = std::make_shared<const UnimplementedNativeViewProps>();
_props = defaultProps;
CGRect bounds = self.bounds;
_label = [[UILabel alloc] initWithFrame:bounds];
_label.backgroundColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.3];
_label.layoutMargins = UIEdgeInsetsMake(12, 12, 12, 12);
_label.lineBreakMode = NSLineBreakByWordWrapping;
_label.numberOfLines = 0;
_label.textAlignment = NSTextAlignmentCenter;
_label.textColor = [UIColor whiteColor];
self.contentView = _label;
}
return self;
}
#pragma mark - RCTComponentViewProtocol
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<UnimplementedNativeViewComponentDescriptor>();
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
const auto &oldViewProps = *std::static_pointer_cast<const UnimplementedNativeViewProps>(_props);
const auto &newViewProps = *std::static_pointer_cast<const UnimplementedNativeViewProps>(props);
if (oldViewProps.name != newViewProps.name) {
_label.text = [NSString stringWithFormat:@"'%s' is not Fabric compatible yet.", newViewProps.name.c_str()];
}
[super updateProps:props oldProps:oldProps];
}
@end

View File

@ -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 all kinds of <UnimplementedView> components.
*/
@interface RCTUnimplementedViewComponentView : RCTViewComponentView
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,73 @@
/*
* 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 "RCTUnimplementedViewComponentView.h"
#import <react/components/rncore/ComponentDescriptors.h>
#import <react/components/rncore/EventEmitters.h>
#import <react/components/rncore/Props.h>
#import <react/components/unimplementedview/UnimplementedViewComponentDescriptor.h>
#import <react/components/unimplementedview/UnimplementedViewShadowNode.h>
#import <React/RCTConversions.h>
#import "FBRCTFabricComponentsPlugins.h"
using namespace facebook::react;
@implementation RCTUnimplementedViewComponentView {
UILabel *_label;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static auto const defaultProps = std::make_shared<UnimplementedViewProps const>();
_props = defaultProps;
_label = [[UILabel alloc] initWithFrame:self.bounds];
_label.backgroundColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.3];
_label.lineBreakMode = NSLineBreakByCharWrapping;
_label.numberOfLines = 0;
_label.textAlignment = NSTextAlignmentCenter;
_label.textColor = [UIColor whiteColor];
_label.allowsDefaultTighteningForTruncation = YES;
_label.adjustsFontSizeToFitWidth = YES;
self.contentView = _label;
}
return self;
}
#pragma mark - RCTComponentViewProtocol
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<UnimplementedViewComponentDescriptor>();
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
auto const &oldUnimplementedViewProps = *std::static_pointer_cast<UnimplementedViewProps const>(_props);
auto const &newUnimplementedViewProps = *std::static_pointer_cast<UnimplementedViewProps const>(props);
if (oldUnimplementedViewProps.getComponentName() != newUnimplementedViewProps.getComponentName()) {
_label.text =
[NSString stringWithFormat:@"Unimplemented component: <%s>", newUnimplementedViewProps.getComponentName()];
}
[super updateProps:props oldProps:oldProps];
}
@end
Class<RCTComponentViewProtocol> RCTUnimplementedNativeViewCls(void)
{
return RCTUnimplementedViewComponentView.class;
}

View File

@ -0,0 +1,90 @@
/*
* 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/RCTComponentViewProtocol.h>
#import <React/RCTTouchableComponentViewProtocol.h>
#import <React/UIView+ComponentViewProtocol.h>
#import <react/components/view/ViewEventEmitter.h>
#import <react/components/view/ViewProps.h>
#import <react/core/EventEmitter.h>
#import <react/core/LayoutMetrics.h>
#import <react/core/Props.h>
NS_ASSUME_NONNULL_BEGIN
/**
* UIView class for <View> component.
*/
@interface RCTViewComponentView : UIView <RCTComponentViewProtocol, RCTTouchableComponentViewProtocol> {
@protected
facebook::react::LayoutMetrics _layoutMetrics;
facebook::react::SharedViewProps _props;
facebook::react::SharedViewEventEmitter _eventEmitter;
}
/**
* Represents the `UIView` instance that is being automatically attached to
* the component view and laid out using on `layoutMetrics` (especially `size`
* and `padding`) of the component.
* This view must not be a component view; it's just a convenient way
* to embed/bridge pure native views as component views.
* Defaults to `nil`. Assign `nil` to remove view as subview.
*/
@property (nonatomic, strong, nullable) UIView *contentView;
/**
* Provides access to `nativeId` prop of the component.
* It might be used by subclasses (which need to refer to the view from
* other platform-specific external views or systems by some id) or
* by debugging/inspection tools.
* Defaults to `nil`.
*/
@property (nonatomic, strong, nullable) NSString *nativeId;
/**
* Provides access to `foregroundColor` prop of the component.
* Must be used by subclasses only.
*/
@property (nonatomic, strong, nullable) UIColor *foregroundColor;
/**
* Returns the object - usually (sub)view - which represents this
* component view in terms of accessibility.
* All accessibility properties will be applied to this object.
* May be overridden in subclass which needs to be accessiblitywise
* transparent in favour of some subview.
* Defaults to `self`.
*/
@property (nonatomic, strong, nullable, readonly) NSObject *accessibilityElement;
/**
* Insets used when hit testing inside this view.
*/
@property (nonatomic, assign) UIEdgeInsets hitTestEdgeInsets;
/**
* Enforcing `call super` semantic for overridden methods from `RCTComponentViewProtocol`.
* The methods update the instance variables.
*/
- (void)updateProps:(facebook::react::Props::Shared const &)props
oldProps:(facebook::react::Props::Shared const &)oldProps NS_REQUIRES_SUPER;
- (void)updateEventEmitter:(facebook::react::EventEmitter::Shared const &)eventEmitter NS_REQUIRES_SUPER;
- (void)updateLayoutMetrics:(facebook::react::LayoutMetrics const &)layoutMetrics
oldLayoutMetrics:(facebook::react::LayoutMetrics const &)oldLayoutMetrics NS_REQUIRES_SUPER;
- (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask NS_REQUIRES_SUPER;
- (void)prepareForRecycle NS_REQUIRES_SUPER;
/*
* This is a fragment of temporary workaround that we need only temporary and will get rid of soon.
*/
- (NSString *)componentViewName_DO_NOT_USE_THIS_IS_BROKEN;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,585 @@
/*
* 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 "RCTViewComponentView.h"
#import <React/RCTAssert.h>
#import <React/RCTBorderDrawing.h>
#import <objc/runtime.h>
#import <react/components/view/ViewComponentDescriptor.h>
#import <react/components/view/ViewEventEmitter.h>
#import <react/components/view/ViewProps.h>
#import "RCTConversions.h"
using namespace facebook::react;
@implementation RCTViewComponentView {
UIColor *_backgroundColor;
CALayer *_borderLayer;
BOOL _needsInvalidateLayer;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static auto const defaultProps = std::make_shared<ViewProps const>();
_props = defaultProps;
}
return self;
}
- (facebook::react::SharedProps)props
{
return _props;
}
- (void)setContentView:(UIView *)contentView
{
if (_contentView) {
[_contentView removeFromSuperview];
}
_contentView = contentView;
if (_contentView) {
[self addSubview:_contentView];
}
}
- (void)layoutSubviews
{
[super layoutSubviews];
// Consider whether using `updateLayoutMetrics:oldLayoutMetrics`
// isn't more appropriate for your use case. `layoutSubviews` is called
// by UIKit while `updateLayoutMetrics:oldLayoutMetrics` is called
// by React Native Renderer within `CATransaction`.
// If you are calling `setFrame:` or other methods that cause
// `layoutSubviews` to be triggered, `_contentView`'s and `_borderLayout`'s
// frames might get out of sync with `self.bounds`.
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
if (UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero)) {
return [super pointInside:point withEvent:event];
}
CGRect hitFrame = UIEdgeInsetsInsetRect(self.bounds, self.hitTestEdgeInsets);
return CGRectContainsPoint(hitFrame, point);
}
- (UIColor *)backgroundColor
{
return _backgroundColor;
}
- (void)setBackgroundColor:(UIColor *)backgroundColor
{
_backgroundColor = backgroundColor;
}
#pragma mark - RCTComponentViewProtocol
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
RCTAssert(
self == [RCTViewComponentView class],
@"`+[RCTComponentViewProtocol componentDescriptorProvider]` must be implemented for all subclasses (and `%@` particularly).",
NSStringFromClass([self class]));
return concreteComponentDescriptorProvider<ViewComponentDescriptor>();
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
#ifndef NS_BLOCK_ASSERTIONS
auto propsRawPtr = _props.get();
RCTAssert(
propsRawPtr &&
([self class] == [RCTViewComponentView class] ||
typeid(*propsRawPtr).hash_code() != typeid(ViewProps const).hash_code()),
@"`RCTViewComponentView` subclasses (and `%@` particularly) must setup `_props`"
" instance variable with a default value in the constructor.",
NSStringFromClass([self class]));
#endif
auto const &oldViewProps = *std::static_pointer_cast<ViewProps const>(_props);
auto const &newViewProps = *std::static_pointer_cast<ViewProps const>(props);
BOOL needsInvalidateLayer = NO;
// `opacity`
if (oldViewProps.opacity != newViewProps.opacity) {
self.layer.opacity = (CGFloat)newViewProps.opacity;
needsInvalidateLayer = YES;
}
// `backgroundColor`
if (oldViewProps.backgroundColor != newViewProps.backgroundColor) {
self.backgroundColor = RCTUIColorFromSharedColor(newViewProps.backgroundColor);
needsInvalidateLayer = YES;
}
// `foregroundColor`
if (oldViewProps.foregroundColor != newViewProps.foregroundColor) {
self.foregroundColor = RCTUIColorFromSharedColor(newViewProps.foregroundColor);
}
// `shadowColor`
if (oldViewProps.shadowColor != newViewProps.shadowColor) {
CGColorRef shadowColor = RCTCGColorRefFromSharedColor(newViewProps.shadowColor);
self.layer.shadowColor = shadowColor;
CGColorRelease(shadowColor);
needsInvalidateLayer = YES;
}
// `shadowOffset`
if (oldViewProps.shadowOffset != newViewProps.shadowOffset) {
self.layer.shadowOffset = RCTCGSizeFromSize(newViewProps.shadowOffset);
needsInvalidateLayer = YES;
}
// `shadowOpacity`
if (oldViewProps.shadowOpacity != newViewProps.shadowOpacity) {
self.layer.shadowOpacity = (CGFloat)newViewProps.shadowOpacity;
needsInvalidateLayer = YES;
}
// `shadowRadius`
if (oldViewProps.shadowRadius != newViewProps.shadowRadius) {
self.layer.shadowRadius = (CGFloat)newViewProps.shadowRadius;
needsInvalidateLayer = YES;
}
// `backfaceVisibility`
if (oldViewProps.backfaceVisibility != newViewProps.backfaceVisibility) {
self.layer.doubleSided = newViewProps.backfaceVisibility == BackfaceVisibility::Visible;
}
// `shouldRasterize`
if (oldViewProps.shouldRasterize != newViewProps.shouldRasterize) {
self.layer.shouldRasterize = newViewProps.shouldRasterize;
self.layer.rasterizationScale = newViewProps.shouldRasterize ? [UIScreen mainScreen].scale : 1.0;
}
// `pointerEvents`
if (oldViewProps.pointerEvents != newViewProps.pointerEvents) {
self.userInteractionEnabled = newViewProps.pointerEvents != PointerEventsMode::None;
}
// `transform`
if (oldViewProps.transform != newViewProps.transform) {
self.layer.transform = RCTCATransform3DFromTransformMatrix(newViewProps.transform);
self.layer.allowsEdgeAntialiasing = newViewProps.transform != Transform::Identity();
}
// `hitSlop`
if (oldViewProps.hitSlop != newViewProps.hitSlop) {
self.hitTestEdgeInsets = RCTUIEdgeInsetsFromEdgeInsets(newViewProps.hitSlop);
}
// `overflow`
if (oldViewProps.getClipsContentToBounds() != newViewProps.getClipsContentToBounds()) {
self.clipsToBounds = newViewProps.getClipsContentToBounds();
needsInvalidateLayer = YES;
}
// `border`
if (oldViewProps.borderStyles != newViewProps.borderStyles || oldViewProps.borderRadii != newViewProps.borderRadii ||
oldViewProps.borderColors != newViewProps.borderColors) {
needsInvalidateLayer = YES;
}
// `nativeId`
if (oldViewProps.nativeId != newViewProps.nativeId) {
self.nativeId = RCTNSStringFromStringNilIfEmpty(newViewProps.nativeId);
}
// `accessible`
if (oldViewProps.accessible != newViewProps.accessible) {
self.accessibilityElement.isAccessibilityElement = newViewProps.accessible;
}
// `accessibilityLabel`
if (oldViewProps.accessibilityLabel != newViewProps.accessibilityLabel) {
self.accessibilityElement.accessibilityLabel = RCTNSStringFromStringNilIfEmpty(newViewProps.accessibilityLabel);
}
// `accessibilityHint`
if (oldViewProps.accessibilityHint != newViewProps.accessibilityHint) {
self.accessibilityElement.accessibilityHint = RCTNSStringFromStringNilIfEmpty(newViewProps.accessibilityHint);
}
// `accessibilityViewIsModal`
if (oldViewProps.accessibilityViewIsModal != newViewProps.accessibilityViewIsModal) {
self.accessibilityElement.accessibilityViewIsModal = newViewProps.accessibilityViewIsModal;
}
// `accessibilityElementsHidden`
if (oldViewProps.accessibilityElementsHidden != newViewProps.accessibilityElementsHidden) {
self.accessibilityElement.accessibilityElementsHidden = newViewProps.accessibilityElementsHidden;
}
if (oldViewProps.accessibilityTraits != newViewProps.accessibilityTraits) {
self.accessibilityElement.accessibilityTraits =
RCTUIAccessibilityTraitsFromAccessibilityTraits(newViewProps.accessibilityTraits);
}
// `accessibilityIgnoresInvertColors`
if (oldViewProps.accessibilityIgnoresInvertColors != newViewProps.accessibilityIgnoresInvertColors) {
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
if (@available(iOS 11.0, *)) {
self.accessibilityIgnoresInvertColors = newViewProps.accessibilityIgnoresInvertColors;
}
#endif
}
_needsInvalidateLayer = _needsInvalidateLayer || needsInvalidateLayer;
_props = std::static_pointer_cast<ViewProps const>(props);
}
- (void)updateEventEmitter:(EventEmitter::Shared const &)eventEmitter
{
assert(std::dynamic_pointer_cast<ViewEventEmitter const>(eventEmitter));
_eventEmitter = std::static_pointer_cast<ViewEventEmitter const>(eventEmitter);
}
- (void)updateLayoutMetrics:(LayoutMetrics const &)layoutMetrics
oldLayoutMetrics:(LayoutMetrics const &)oldLayoutMetrics
{
// Using stored `_layoutMetrics` as `oldLayoutMetrics` here to avoid
// re-applying individual sub-values which weren't changed.
[super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:_layoutMetrics];
_layoutMetrics = layoutMetrics;
_needsInvalidateLayer = YES;
if (_borderLayer) {
_borderLayer.frame = self.layer.bounds;
}
if (_contentView) {
_contentView.frame = RCTCGRectFromRect(_layoutMetrics.getContentFrame());
}
}
- (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask
{
[super finalizeUpdates:updateMask];
if (!_needsInvalidateLayer) {
return;
}
_needsInvalidateLayer = NO;
[self invalidateLayer];
}
- (void)prepareForRecycle
{
[super prepareForRecycle];
_eventEmitter.reset();
}
- (UIView *)betterHitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// This is a classic textbook implementation of `hitTest:` with a couple of improvements:
// * It does not stop algorithm if some touch is outside the view
// which does not have `clipToBounds` enabled.
// * Taking `layer.zIndex` field into an account is not required because
// lists of `ShadowView`s are already sorted based on `zIndex` prop.
if (!self.userInteractionEnabled || self.hidden || self.alpha < 0.01) {
return nil;
}
BOOL isPointInside = [self pointInside:point withEvent:event];
if (self.clipsToBounds && !isPointInside) {
return nil;
}
for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
UIView *hitView = [subview hitTest:[subview convertPoint:point fromView:self] withEvent:event];
if (hitView) {
return hitView;
}
}
return isPointInside ? self : nil;
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
switch (_props->pointerEvents) {
case PointerEventsMode::Auto:
return [self betterHitTest:point withEvent:event];
case PointerEventsMode::None:
return nil;
case PointerEventsMode::BoxOnly:
return [self pointInside:point withEvent:event] ? self : nil;
case PointerEventsMode::BoxNone:
UIView *view = [self betterHitTest:point withEvent:event];
return view != self ? view : nil;
}
}
static RCTCornerRadii RCTCornerRadiiFromBorderRadii(BorderRadii borderRadii)
{
return RCTCornerRadii{.topLeft = (CGFloat)borderRadii.topLeft,
.topRight = (CGFloat)borderRadii.topRight,
.bottomLeft = (CGFloat)borderRadii.bottomLeft,
.bottomRight = (CGFloat)borderRadii.bottomRight};
}
static RCTBorderColors RCTBorderColorsFromBorderColors(BorderColors borderColors)
{
return RCTBorderColors{.left = RCTCGColorRefUnretainedFromSharedColor(borderColors.left),
.top = RCTCGColorRefUnretainedFromSharedColor(borderColors.top),
.bottom = RCTCGColorRefUnretainedFromSharedColor(borderColors.bottom),
.right = RCTCGColorRefUnretainedFromSharedColor(borderColors.right)};
}
static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle)
{
switch (borderStyle) {
case BorderStyle::Solid:
return RCTBorderStyleSolid;
case BorderStyle::Dotted:
return RCTBorderStyleDotted;
case BorderStyle::Dashed:
return RCTBorderStyleDashed;
}
}
- (void)invalidateLayer
{
CALayer *layer = self.layer;
if (CGSizeEqualToSize(layer.bounds.size, CGSizeZero)) {
return;
}
auto const borderMetrics = _props->resolveBorderMetrics(_layoutMetrics);
// Stage 1. Shadow Path
BOOL const layerHasShadow = layer.shadowOpacity > 0 && CGColorGetAlpha(layer.shadowColor) > 0;
if (layerHasShadow) {
if (CGColorGetAlpha(_backgroundColor.CGColor) > 0.999) {
// If view has a solid background color, calculate shadow path from border.
RCTCornerInsets const cornerInsets =
RCTGetCornerInsets(RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), UIEdgeInsetsZero);
CGPathRef shadowPath = RCTPathCreateWithRoundedRect(self.bounds, cornerInsets, nil);
layer.shadowPath = shadowPath;
CGPathRelease(shadowPath);
} else {
// Can't accurately calculate box shadow, so fall back to pixel-based shadow.
layer.shadowPath = nil;
}
} else {
layer.shadowPath = nil;
}
// Stage 2. Border Rendering
bool const useCoreAnimationBorderRendering =
borderMetrics.borderColors.isUniform() && borderMetrics.borderWidths.isUniform() &&
borderMetrics.borderStyles.isUniform() && borderMetrics.borderRadii.isUniform() &&
borderMetrics.borderStyles.left == BorderStyle::Solid &&
(
// iOS draws borders in front of the content whereas CSS draws them behind
// the content. For this reason, only use iOS border drawing when clipping
// or when the border is hidden.
borderMetrics.borderWidths.left == 0 ||
colorComponentsFromColor(borderMetrics.borderColors.left).alpha == 0 || self.clipsToBounds);
if (useCoreAnimationBorderRendering) {
layer.mask = nil;
if (_borderLayer) {
[_borderLayer removeFromSuperlayer];
_borderLayer = nil;
}
layer.borderWidth = (CGFloat)borderMetrics.borderWidths.left;
CGColorRef borderColor = RCTCGColorRefFromSharedColor(borderMetrics.borderColors.left);
layer.borderColor = borderColor;
CGColorRelease(borderColor);
layer.cornerRadius = (CGFloat)borderMetrics.borderRadii.topLeft;
layer.backgroundColor = _backgroundColor.CGColor;
} else {
if (!_borderLayer) {
_borderLayer = [[CALayer alloc] init];
_borderLayer.zPosition = -1024.0f;
_borderLayer.frame = layer.bounds;
_borderLayer.magnificationFilter = kCAFilterNearest;
[layer addSublayer:_borderLayer];
}
layer.backgroundColor = nil;
layer.borderWidth = 0;
layer.borderColor = nil;
layer.cornerRadius = 0;
UIImage *image = RCTGetBorderImage(
RCTBorderStyleFromBorderStyle(borderMetrics.borderStyles.left),
layer.bounds.size,
RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii),
RCTUIEdgeInsetsFromEdgeInsets(borderMetrics.borderWidths),
RCTBorderColorsFromBorderColors(borderMetrics.borderColors),
_backgroundColor.CGColor,
self.clipsToBounds);
if (image == nil) {
_borderLayer.contents = nil;
} else {
CGSize imageSize = image.size;
UIEdgeInsets imageCapInsets = image.capInsets;
CGRect contentsCenter =
CGRect{CGPoint{imageCapInsets.left / imageSize.width, imageCapInsets.top / imageSize.height},
CGSize{(CGFloat)1.0 / imageSize.width, (CGFloat)1.0 / imageSize.height}};
_borderLayer.contents = (id)image.CGImage;
_borderLayer.contentsScale = image.scale;
BOOL isResizable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero);
if (isResizable) {
_borderLayer.contentsCenter = contentsCenter;
} else {
_borderLayer.contentsCenter = CGRect{CGPoint{0.0, 0.0}, CGSize{1.0, 1.0}};
}
}
// Stage 2.5. Custom Clipping Mask
CAShapeLayer *maskLayer = nil;
CGFloat cornerRadius = 0;
if (self.clipsToBounds) {
if (borderMetrics.borderRadii.isUniform()) {
// In this case we can simply use `cornerRadius` exclusively.
cornerRadius = borderMetrics.borderRadii.topLeft;
} else {
// In this case we have to generate masking layer manually.
CGPathRef path = RCTPathCreateWithRoundedRect(
self.bounds,
RCTGetCornerInsets(RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), UIEdgeInsetsZero),
nil);
maskLayer = [CAShapeLayer layer];
maskLayer.path = path;
CGPathRelease(path);
}
}
layer.cornerRadius = cornerRadius;
layer.mask = maskLayer;
}
}
#pragma mark - Accessibility
- (NSObject *)accessibilityElement
{
return self;
}
static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
{
NSMutableString *result = [NSMutableString stringWithString:@""];
for (UIView *subview in view.subviews) {
NSString *label = subview.accessibilityLabel;
if (!label) {
label = RCTRecursiveAccessibilityLabel(subview);
}
if (label && label.length > 0) {
if (result.length > 0) {
[result appendString:@" "];
}
[result appendString:label];
}
}
return result;
}
- (NSString *)accessibilityLabel
{
NSString *label = super.accessibilityLabel;
if (label) {
return label;
}
return RCTRecursiveAccessibilityLabel(self);
}
#pragma mark - Accessibility Events
- (NSArray<UIAccessibilityCustomAction *> *)accessibilityCustomActions
{
auto const &accessibilityActions = _props->accessibilityActions;
if (accessibilityActions.size() == 0) {
return nil;
}
NSMutableArray<UIAccessibilityCustomAction *> *customActions = [NSMutableArray array];
for (auto const &accessibilityAction : accessibilityActions) {
[customActions
addObject:[[UIAccessibilityCustomAction alloc] initWithName:RCTNSStringFromString(accessibilityAction)
target:self
selector:@selector(didActivateAccessibilityCustomAction:)]];
}
return [customActions copy];
}
- (BOOL)accessibilityActivate
{
if (_eventEmitter && _props->onAccessibilityTap) {
_eventEmitter->onAccessibilityTap();
return YES;
} else {
return NO;
}
}
- (BOOL)accessibilityPerformMagicTap
{
if (_eventEmitter && _props->onAccessibilityMagicTap) {
_eventEmitter->onAccessibilityMagicTap();
return YES;
} else {
return NO;
}
}
- (BOOL)accessibilityPerformEscape
{
if (_eventEmitter && _props->onAccessibilityEscape) {
_eventEmitter->onAccessibilityEscape();
return YES;
} else {
return NO;
}
}
- (BOOL)didActivateAccessibilityCustomAction:(UIAccessibilityCustomAction *)action
{
if (_eventEmitter && _props->onAccessibilityAction) {
_eventEmitter->onAccessibilityAction(RCTStringFromNSString(action.name));
return YES;
} else {
return NO;
}
}
- (SharedTouchEventEmitter)touchEventEmitterAtPoint:(CGPoint)point
{
return _eventEmitter;
}
- (NSString *)componentViewName_DO_NOT_USE_THIS_IS_BROKEN
{
return RCTNSStringFromString([[self class] componentDescriptorProvider].name);
}
@end

View File

@ -0,0 +1,32 @@
/*
* 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/RCTComponentViewProtocol.h>
NS_ASSUME_NONNULL_BEGIN
/*
* Holds a native view class and a set of attributes associated with it.
*/
class RCTComponentViewClassDescriptor final {
public:
/*
* Associated (and owned) native view class.
*/
Class<RCTComponentViewProtocol> viewClass;
/*
* Indicates a requirement to call on the view methods from
* `RCTMountingTransactionObserving` protocol.
*/
bool observesMountingTransactionWillMount{false};
bool observesMountingTransactionDidMount{false};
};
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,52 @@
/*
* 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/RCTComponentViewProtocol.h>
NS_ASSUME_NONNULL_BEGIN
/*
* Holds a native view instance and a set of attributes associated with it.
* Mounting infrastructure uses these objects to bookkeep views and cache their
* attributes for efficient access.
*/
class RCTComponentViewDescriptor final {
public:
/*
* Associated (and owned) native view instance.
*/
__strong UIView<RCTComponentViewProtocol> *view = nil;
/*
* Indicates a requirement to call on the view methods from
* `RCTMountingTransactionObserving` protocol.
*/
bool observesMountingTransactionWillMount{false};
bool observesMountingTransactionDidMount{false};
};
inline bool operator==(RCTComponentViewDescriptor const &lhs, RCTComponentViewDescriptor const &rhs)
{
return lhs.view == rhs.view;
}
inline bool operator!=(RCTComponentViewDescriptor const &lhs, RCTComponentViewDescriptor const &rhs)
{
return lhs.view != rhs.view;
}
template <>
struct std::hash<RCTComponentViewDescriptor> {
size_t operator()(RCTComponentViewDescriptor const &componentViewDescriptor) const
{
return std::hash<void *>()((__bridge void *)componentViewDescriptor.view);
}
};
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,47 @@
/*
* 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/RCTComponentViewDescriptor.h>
#import <React/RCTComponentViewProtocol.h>
#import <react/uimanager/ComponentDescriptorRegistry.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Registry of supported component view classes that can instantiate
* view component instances by given component handle.
*/
@interface RCTComponentViewFactory : NSObject
/**
* Constructs and returns an instance of the class with a bunch of already registered standard components.
*/
+ (RCTComponentViewFactory *)standardComponentViewFactory;
/**
* Registers a component view class in the factory.
*/
- (void)registerComponentViewClass:(Class<RCTComponentViewProtocol>)componentViewClass;
/**
* Creates a component view with given component handle.
*/
- (RCTComponentViewDescriptor)createComponentViewWithComponentHandle:(facebook::react::ComponentHandle)componentHandle;
/**
* Creates *managed* `ComponentDescriptorRegistry`. After creation, the object continues to store a weak pointer to the
* registry and update it accordingly to the changes in the object.
*/
- (facebook::react::ComponentDescriptorRegistry::Shared)createComponentDescriptorRegistryWithParameters:
(facebook::react::ComponentDescriptorParameters)parameters;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,162 @@
/*
* 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 "RCTComponentViewFactory.h"
#import <React/RCTAssert.h>
#import <React/RCTConversions.h>
#import <better/map.h>
#import <better/mutex.h>
#import <react/core/ReactPrimitives.h>
#import <react/uimanager/ComponentDescriptorProviderRegistry.h>
#ifdef RN_DISABLE_OSS_PLUGIN_HEADER
#import <RCTFabricComponentPlugin/RCTFabricPluginProvider.h>
#else
#import "RCTFabricComponentsPlugins.h"
#endif
#import "RCTComponentViewClassDescriptor.h"
#import "RCTFabricComponentsPlugins.h"
#import "RCTImageComponentView.h"
#import "RCTLegacyViewManagerInteropComponentView.h"
#import "RCTMountingTransactionObserving.h"
#import "RCTParagraphComponentView.h"
#import "RCTRootComponentView.h"
#import "RCTTextInputComponentView.h"
#import "RCTUnimplementedViewComponentView.h"
#import "RCTViewComponentView.h"
#import <objc/runtime.h>
using namespace facebook::react;
static Class<RCTComponentViewProtocol> RCTComponentViewClassWithName(const char *componentName)
{
return RCTFabricComponentsProvider(componentName);
}
@implementation RCTComponentViewFactory {
better::map<ComponentHandle, RCTComponentViewClassDescriptor> _componentViewClasses;
ComponentDescriptorProviderRegistry _providerRegistry;
better::shared_mutex _mutex;
}
+ (RCTComponentViewFactory *)standardComponentViewFactory
{
RCTComponentViewFactory *componentViewFactory = [[RCTComponentViewFactory alloc] init];
[componentViewFactory registerComponentViewClass:[RCTRootComponentView class]];
[componentViewFactory registerComponentViewClass:[RCTViewComponentView class]];
[componentViewFactory registerComponentViewClass:[RCTParagraphComponentView class]];
[componentViewFactory registerComponentViewClass:[RCTTextInputComponentView class]];
Class<RCTComponentViewProtocol> imageClass = RCTComponentViewClassWithName("Image");
[componentViewFactory registerComponentViewClass:imageClass];
auto providerRegistry = &componentViewFactory->_providerRegistry;
providerRegistry->setComponentDescriptorProviderRequest(
[providerRegistry, componentViewFactory](ComponentName requestedComponentName) {
// Fallback 1: Call provider function for component view class.
Class<RCTComponentViewProtocol> klass = RCTComponentViewClassWithName(requestedComponentName);
if (klass) {
[componentViewFactory registerComponentViewClass:klass];
return;
}
// Fallback 2: Try to use Paper Interop.
if ([RCTLegacyViewManagerInteropComponentView isSupported:RCTNSStringFromString(requestedComponentName)]) {
auto flavor = std::make_shared<std::string const>(requestedComponentName);
auto componentName = ComponentName{flavor->c_str()};
auto componentHandle = reinterpret_cast<ComponentHandle>(componentName);
auto constructor = [RCTLegacyViewManagerInteropComponentView componentDescriptorProvider].constructor;
providerRegistry->add(ComponentDescriptorProvider{componentHandle, componentName, flavor, constructor});
componentViewFactory->_componentViewClasses[componentHandle] = [componentViewFactory
_componentViewClassDescriptorFromClass:[RCTLegacyViewManagerInteropComponentView class]];
return;
}
// Fallback 3: Finally use <UnimplementedView>.
auto flavor = std::make_shared<std::string const>(requestedComponentName);
auto componentName = ComponentName{flavor->c_str()};
auto componentHandle = reinterpret_cast<ComponentHandle>(componentName);
auto constructor = [RCTUnimplementedViewComponentView componentDescriptorProvider].constructor;
providerRegistry->add(ComponentDescriptorProvider{componentHandle, componentName, flavor, constructor});
componentViewFactory->_componentViewClasses[componentHandle] =
[componentViewFactory _componentViewClassDescriptorFromClass:[RCTUnimplementedViewComponentView class]];
});
return componentViewFactory;
}
- (RCTComponentViewClassDescriptor)_componentViewClassDescriptorFromClass:(Class<RCTComponentViewProtocol>)viewClass
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
return RCTComponentViewClassDescriptor
{
.viewClass = viewClass,
.observesMountingTransactionWillMount =
(bool)class_respondsToSelector(viewClass, @selector(mountingTransactionWillMountWithMetadata:)),
.observesMountingTransactionDidMount =
(bool)class_respondsToSelector(viewClass, @selector(mountingTransactionDidMountWithMetadata:)),
};
#pragma clang diagnostic pop
}
- (void)registerComponentViewClass:(Class<RCTComponentViewProtocol>)componentViewClass
{
std::unique_lock<better::shared_mutex> lock(_mutex);
auto componentDescriptorProvider = [componentViewClass componentDescriptorProvider];
_componentViewClasses[componentDescriptorProvider.handle] =
[self _componentViewClassDescriptorFromClass:componentViewClass];
_providerRegistry.add(componentDescriptorProvider);
auto supplementalComponentDescriptorProviders = [componentViewClass supplementalComponentDescriptorProviders];
for (const auto &provider : supplementalComponentDescriptorProviders) {
_providerRegistry.add(provider);
}
}
- (RCTComponentViewDescriptor)createComponentViewWithComponentHandle:(facebook::react::ComponentHandle)componentHandle
{
RCTAssertMainQueue();
std::shared_lock<better::shared_mutex> lock(_mutex);
auto iterator = _componentViewClasses.find(componentHandle);
RCTAssert(
iterator != _componentViewClasses.end(),
@"ComponentView with componentHandle `%lli` (`%s`) not found.",
componentHandle,
(char *)componentHandle);
auto componentViewClassDescriptor = iterator->second;
Class viewClass = componentViewClassDescriptor.viewClass;
return RCTComponentViewDescriptor{
.view = [[viewClass alloc] init],
.observesMountingTransactionWillMount = componentViewClassDescriptor.observesMountingTransactionWillMount,
.observesMountingTransactionDidMount = componentViewClassDescriptor.observesMountingTransactionDidMount,
};
}
- (facebook::react::ComponentDescriptorRegistry::Shared)createComponentDescriptorRegistryWithParameters:
(facebook::react::ComponentDescriptorParameters)parameters
{
std::shared_lock<better::shared_mutex> lock(_mutex);
return _providerRegistry.createComponentDescriptorRegistry(parameters);
}
@end

View File

@ -0,0 +1,122 @@
/*
* 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/RCTPrimitives.h>
#import <react/core/EventEmitter.h>
#import <react/core/LayoutMetrics.h>
#import <react/core/Props.h>
#import <react/core/State.h>
#import <react/uimanager/ComponentDescriptorProvider.h>
NS_ASSUME_NONNULL_BEGIN
/*
* Bitmask for all types of possible updates performing during mounting.
*/
typedef NS_OPTIONS(NSInteger, RNComponentViewUpdateMask) {
RNComponentViewUpdateMaskNone = 0,
RNComponentViewUpdateMaskProps = 1 << 0,
RNComponentViewUpdateMaskEventEmitter = 1 << 1,
RNComponentViewUpdateMaskState = 1 << 3,
RNComponentViewUpdateMaskLayoutMetrics = 1 << 4,
RNComponentViewUpdateMaskAll = RNComponentViewUpdateMaskProps | RNComponentViewUpdateMaskEventEmitter |
RNComponentViewUpdateMaskState | RNComponentViewUpdateMaskLayoutMetrics
};
/*
* Represents a `UIView` instance managed by React.
* All methods are non-@optional.
* `UIView+ComponentViewProtocol` category provides default implementation
* for all of them.
*/
@protocol RCTComponentViewProtocol <NSObject>
/*
* Returns a `ComponentDescriptorProvider` of a particular `ComponentDescriptor` which this component view
* represents.
*/
+ (facebook::react::ComponentDescriptorProvider)componentDescriptorProvider;
/*
* Returns a list of supplemental `ComponentDescriptorProvider`s (with do not have `ComponentView` counterparts) that
* require for this component view.
*/
+ (std::vector<facebook::react::ComponentDescriptorProvider>)supplementalComponentDescriptorProviders;
/*
* Called for mounting (attaching) a child component view inside `self`
* component view.
* Receiver must add `childComponentView` as a subview.
*/
- (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index;
/*
* Called for unmounting (detaching) a child component view from `self`
* component view.
* Receiver must remove `childComponentView` as a subview.
*/
- (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index;
/*
* Called for updating component's props.
* Receiver must update native view props accordingly changed props.
*/
- (void)updateProps:(facebook::react::Props::Shared const &)props
oldProps:(facebook::react::Props::Shared const &)oldProps;
/*
* Called for updating component's state.
* Receiver must update native view according to changed state.
*/
- (void)updateState:(facebook::react::State::Shared const &)state
oldState:(facebook::react::State::Shared const &)oldState;
/*
* Called for updating component's event handlers set.
* Receiver must cache `eventEmitter` object inside and use it for emitting
* events when needed.
*/
- (void)updateEventEmitter:(facebook::react::EventEmitter::Shared const &)eventEmitter;
/*
* Called for updating component's layout metrics.
* Receiver must update `UIView` layout-related fields (such as `frame`,
* `bounds`, `layer.zPosition`, and so on) accordingly.
*/
- (void)updateLayoutMetrics:(facebook::react::LayoutMetrics const &)layoutMetrics
oldLayoutMetrics:(facebook::react::LayoutMetrics const &)oldLayoutMetrics;
/*
* Called when receiving a command
*/
- (void)handleCommand:(NSString const *)commandName args:(NSArray const *)args;
/*
* Called right after all update methods were called for a particular component view.
* Useful for performing updates that require knowledge of several independent aspects of the compound mounting change
* (e.g. props *and* layout constraints).
*/
- (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask;
/*
* Called right after the component view is moved to a recycle pool.
* Receiver must reset any local state and release associated
* non-reusable resources.
*/
- (void)prepareForRecycle;
/**
* Read the last props used to update the view.
*/
- (facebook::react::SharedProps)props;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,59 @@
/*
* 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/RCTComponentViewDescriptor.h>
#import <React/RCTComponentViewFactory.h>
#import <React/RCTComponentViewProtocol.h>
#import <react/core/ReactPrimitives.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Registry of native component views.
* Provides basic functionality for allocation, recycling, and querying (by tag) native view instances.
*/
@interface RCTComponentViewRegistry : NSObject
@property (nonatomic, strong, readonly) RCTComponentViewFactory *componentViewFactory;
/**
* Returns a descriptor referring to a native view instance from the recycle pool (or being created on demand)
* for given `componentHandle` and with given `tag`.
* #RefuseSingleUse
*/
- (RCTComponentViewDescriptor)dequeueComponentViewWithComponentHandle:(facebook::react::ComponentHandle)componentHandle
tag:(facebook::react::Tag)tag;
/**
* Puts a given native component view to the recycle pool.
* #RefuseSingleUse
*/
- (void)enqueueComponentViewWithComponentHandle:(facebook::react::ComponentHandle)componentHandle
tag:(facebook::react::Tag)tag
componentViewDescriptor:(RCTComponentViewDescriptor)componentViewDescriptor;
/**
* Returns a component view descriptor by given `tag`.
*/
- (RCTComponentViewDescriptor const &)componentViewDescriptorWithTag:(facebook::react::Tag)tag;
/**
* Finds a native component view by given `tag`.
* Returns `nil` if there is no registered component with the `tag`.
*/
- (nullable UIView<RCTComponentViewProtocol> *)findComponentViewWithTag:(facebook::react::Tag)tag;
/**
* Creates a component view with a given type and puts it to the recycle pool.
*/
- (void)optimisticallyCreateComponentViewWithComponentHandle:(facebook::react::ComponentHandle)componentHandle;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,221 @@
/*
* 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 "RCTComponentViewRegistry.h"
#import <Foundation/NSMapTable.h>
#import <React/RCTAssert.h>
#import "RCTImageComponentView.h"
#import "RCTParagraphComponentView.h"
#import "RCTViewComponentView.h"
#import <better/map.h>
using namespace facebook::react;
#define LEGACY_UIMANAGER_INTEGRATION_ENABLED 1
#ifdef LEGACY_UIMANAGER_INTEGRATION_ENABLED
#import <React/RCTBridge+Private.h>
#import <React/RCTUIManager.h>
/**
* Warning: This is a total hack and temporary solution.
* Unless we have a pure Fabric-based implementation of UIManager commands
* delivery pipeline, we have to leverage existing infra. This code tricks
* legacy UIManager by registering all Fabric-managed views in it,
* hence existing command-delivery infra can reach "foreign" views using
* the old pipeline.
*/
@interface RCTUIManager ()
- (NSMutableDictionary<NSNumber *, UIView *> *)viewRegistry;
@end
@interface RCTUIManager (Hack)
+ (void)registerView:(UIView *)view;
+ (void)unregisterView:(UIView *)view;
@end
@implementation RCTUIManager (Hack)
+ (void)registerView:(UIView *)view
{
if (!view) {
return;
}
RCTUIManager *uiManager = [[RCTBridge currentBridge] uiManager];
view.reactTag = @(view.tag);
[uiManager.viewRegistry setObject:view forKey:@(view.tag)];
}
+ (void)unregisterView:(UIView *)view
{
if (!view) {
return;
}
RCTUIManager *uiManager = [[RCTBridge currentBridge] uiManager];
view.reactTag = nil;
[uiManager.viewRegistry removeObjectForKey:@(view.tag)];
}
@end
#endif
const NSInteger RCTComponentViewRegistryRecyclePoolMaxSize = 1024;
@implementation RCTComponentViewRegistry {
better::map<Tag, RCTComponentViewDescriptor> _registry;
better::map<ComponentHandle, std::vector<RCTComponentViewDescriptor>> _recyclePool;
}
- (instancetype)init
{
if (self = [super init]) {
_componentViewFactory = [RCTComponentViewFactory standardComponentViewFactory];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleApplicationDidReceiveMemoryWarningNotification)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// Calling this a bit later, when the main thread is probably idle while JavaScript thread is busy.
[self preallocateViewComponents];
});
}
return self;
}
- (void)preallocateViewComponents
{
// This data is based on empirical evidence which should represent the reality pretty well.
// Regular `<View>` has magnitude equals to `1` by definition.
std::vector<std::pair<ComponentHandle, float>> componentMagnitudes = {
{[RCTViewComponentView componentDescriptorProvider].handle, 1},
{[RCTImageComponentView componentDescriptorProvider].handle, 0.3},
{[RCTParagraphComponentView componentDescriptorProvider].handle, 0.3},
};
// `complexity` represents the complexity of a typical surface in a number of `<View>` components (with Flattening
// enabled).
float complexity = 100;
// The whole process should not take more than 10ms in the worst case, so there is no need to split it up.
for (const auto &componentMagnitude : componentMagnitudes) {
for (int i = 0; i < complexity * componentMagnitude.second; i++) {
[self optimisticallyCreateComponentViewWithComponentHandle:componentMagnitude.first];
}
}
}
- (RCTComponentViewDescriptor)dequeueComponentViewWithComponentHandle:(ComponentHandle)componentHandle tag:(Tag)tag
{
RCTAssertMainQueue();
RCTAssert(
_registry.find(tag) == _registry.end(),
@"RCTComponentViewRegistry: Attempt to dequeue already registered component.");
auto componentViewDescriptor = [self _dequeueComponentViewWithComponentHandle:componentHandle];
componentViewDescriptor.view.tag = tag;
_registry.insert({tag, componentViewDescriptor});
#ifdef LEGACY_UIMANAGER_INTEGRATION_ENABLED
[RCTUIManager registerView:componentViewDescriptor.view];
#endif
return componentViewDescriptor;
}
- (void)enqueueComponentViewWithComponentHandle:(ComponentHandle)componentHandle
tag:(Tag)tag
componentViewDescriptor:(RCTComponentViewDescriptor)componentViewDescriptor
{
RCTAssertMainQueue();
RCTAssert(
_registry.find(tag) != _registry.end(), @"RCTComponentViewRegistry: Attempt to enqueue unregistered component.");
#ifdef LEGACY_UIMANAGER_INTEGRATION_ENABLED
[RCTUIManager unregisterView:componentViewDescriptor.view];
#endif
_registry.erase(tag);
componentViewDescriptor.view.tag = 0;
[self _enqueueComponentViewWithComponentHandle:componentHandle componentViewDescriptor:componentViewDescriptor];
}
- (void)optimisticallyCreateComponentViewWithComponentHandle:(ComponentHandle)componentHandle
{
RCTAssertMainQueue();
[self _enqueueComponentViewWithComponentHandle:componentHandle
componentViewDescriptor:[self.componentViewFactory
createComponentViewWithComponentHandle:componentHandle]];
}
- (RCTComponentViewDescriptor const &)componentViewDescriptorWithTag:(Tag)tag
{
RCTAssertMainQueue();
auto iterator = _registry.find(tag);
RCTAssert(iterator != _registry.end(), @"RCTComponentViewRegistry: Attempt to query unregistered component.");
return iterator->second;
}
- (nullable UIView<RCTComponentViewProtocol> *)findComponentViewWithTag:(Tag)tag
{
RCTAssertMainQueue();
auto iterator = _registry.find(tag);
if (iterator == _registry.end()) {
return nil;
}
return iterator->second.view;
}
- (RCTComponentViewDescriptor)_dequeueComponentViewWithComponentHandle:(ComponentHandle)componentHandle
{
RCTAssertMainQueue();
auto &recycledViews = _recyclePool[componentHandle];
if (recycledViews.size() == 0) {
return [self.componentViewFactory createComponentViewWithComponentHandle:componentHandle];
}
auto componentViewDescriptor = recycledViews.back();
recycledViews.pop_back();
return componentViewDescriptor;
}
- (void)_enqueueComponentViewWithComponentHandle:(ComponentHandle)componentHandle
componentViewDescriptor:(RCTComponentViewDescriptor)componentViewDescriptor
{
RCTAssertMainQueue();
auto &recycledViews = _recyclePool[componentHandle];
if (recycledViews.size() > RCTComponentViewRegistryRecyclePoolMaxSize) {
return;
}
[componentViewDescriptor.view prepareForRecycle];
recycledViews.push_back(componentViewDescriptor);
}
- (void)handleApplicationDidReceiveMemoryWarningNotification
{
_recyclePool.clear();
}
@end

View File

@ -0,0 +1,46 @@
/*
* 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/RCTMountingManagerDelegate.h>
#import <React/RCTPrimitives.h>
#import <react/core/ComponentDescriptor.h>
#import <react/core/ReactPrimitives.h>
#import <react/mounting/MountingCoordinator.h>
#import <react/mounting/ShadowView.h>
NS_ASSUME_NONNULL_BEGIN
@class RCTComponentViewRegistry;
/**
* Manages mounting process.
*/
@interface RCTMountingManager : NSObject
@property (nonatomic, weak) id<RCTMountingManagerDelegate> delegate;
@property (nonatomic, strong) RCTComponentViewRegistry *componentViewRegistry;
/**
* Schedule a mounting transaction to be performed on the main thread.
* Can be called from any thread.
*/
- (void)scheduleTransaction:(facebook::react::MountingCoordinator::Shared const &)mountingCoordinator;
/**
* Dispatch a command to be performed on the main thread.
* Can be called from any thread.
*/
- (void)dispatchCommand:(ReactTag)reactTag commandName:(NSString *)commandName args:(NSArray *)args;
- (void)synchronouslyUpdateViewOnUIThread:(ReactTag)reactTag
changedProps:(NSDictionary *)props
componentDescriptor:(const facebook::react::ComponentDescriptor &)componentDescriptor;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,313 @@
/*
* 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 "RCTMountingManager.h"
#import <better/map.h>
#import <React/RCTAssert.h>
#import <React/RCTFollyConvert.h>
#import <React/RCTUtils.h>
#import <react/core/LayoutableShadowNode.h>
#import <react/core/RawProps.h>
#import <react/debug/SystraceSection.h>
#import "RCTComponentViewProtocol.h"
#import "RCTComponentViewRegistry.h"
#import "RCTConversions.h"
#import "RCTMountingTransactionObserverCoordinator.h"
using namespace facebook;
using namespace facebook::react;
// `Create` instruction
static void RNCreateMountInstruction(
ShadowViewMutation const &mutation,
RCTComponentViewRegistry *registry,
RCTMountingTransactionObserverCoordinator &observerCoordinator,
SurfaceId surfaceId)
{
auto componentViewDescriptor =
[registry dequeueComponentViewWithComponentHandle:mutation.newChildShadowView.componentHandle
tag:mutation.newChildShadowView.tag];
observerCoordinator.registerViewComponentDescriptor(componentViewDescriptor, surfaceId);
}
// `Delete` instruction
static void RNDeleteMountInstruction(
ShadowViewMutation const &mutation,
RCTComponentViewRegistry *registry,
RCTMountingTransactionObserverCoordinator &observerCoordinator,
SurfaceId surfaceId)
{
auto const &oldChildShadowView = mutation.oldChildShadowView;
auto const &componentViewDescriptor = [registry componentViewDescriptorWithTag:oldChildShadowView.tag];
observerCoordinator.unregisterViewComponentDescriptor(componentViewDescriptor, surfaceId);
[registry enqueueComponentViewWithComponentHandle:oldChildShadowView.componentHandle
tag:oldChildShadowView.tag
componentViewDescriptor:componentViewDescriptor];
}
// `Insert` instruction
static void RNInsertMountInstruction(ShadowViewMutation const &mutation, RCTComponentViewRegistry *registry)
{
auto const &newShadowView = mutation.newChildShadowView;
auto const &parentShadowView = mutation.parentShadowView;
auto const &childComponentViewDescriptor = [registry componentViewDescriptorWithTag:newShadowView.tag];
auto const &parentComponentViewDescriptor = [registry componentViewDescriptorWithTag:parentShadowView.tag];
[parentComponentViewDescriptor.view mountChildComponentView:childComponentViewDescriptor.view index:mutation.index];
}
// `Remove` instruction
static void RNRemoveMountInstruction(ShadowViewMutation const &mutation, RCTComponentViewRegistry *registry)
{
auto const &oldShadowView = mutation.oldChildShadowView;
auto const &parentShadowView = mutation.parentShadowView;
auto const &childComponentViewDescriptor = [registry componentViewDescriptorWithTag:oldShadowView.tag];
auto const &parentComponentViewDescriptor = [registry componentViewDescriptorWithTag:parentShadowView.tag];
[parentComponentViewDescriptor.view unmountChildComponentView:childComponentViewDescriptor.view index:mutation.index];
}
// `Update Props` instruction
static void RNUpdatePropsMountInstruction(ShadowViewMutation const &mutation, RCTComponentViewRegistry *registry)
{
auto const &oldShadowView = mutation.oldChildShadowView;
auto const &newShadowView = mutation.newChildShadowView;
auto const &componentViewDescriptor = [registry componentViewDescriptorWithTag:newShadowView.tag];
[componentViewDescriptor.view updateProps:newShadowView.props oldProps:oldShadowView.props];
}
// `Update EventEmitter` instruction
static void RNUpdateEventEmitterMountInstruction(ShadowViewMutation const &mutation, RCTComponentViewRegistry *registry)
{
auto const &newShadowView = mutation.newChildShadowView;
auto const &componentViewDescriptor = [registry componentViewDescriptorWithTag:newShadowView.tag];
[componentViewDescriptor.view updateEventEmitter:newShadowView.eventEmitter];
}
// `Update LayoutMetrics` instruction
static void RNUpdateLayoutMetricsMountInstruction(
ShadowViewMutation const &mutation,
RCTComponentViewRegistry *registry)
{
auto const &oldShadowView = mutation.oldChildShadowView;
auto const &newShadowView = mutation.newChildShadowView;
auto const &componentViewDescriptor = [registry componentViewDescriptorWithTag:newShadowView.tag];
[componentViewDescriptor.view updateLayoutMetrics:newShadowView.layoutMetrics
oldLayoutMetrics:oldShadowView.layoutMetrics];
}
// `Update State` instruction
static void RNUpdateStateMountInstruction(ShadowViewMutation const &mutation, RCTComponentViewRegistry *registry)
{
auto const &oldShadowView = mutation.oldChildShadowView;
auto const &newShadowView = mutation.newChildShadowView;
auto const &componentViewDescriptor = [registry componentViewDescriptorWithTag:newShadowView.tag];
[componentViewDescriptor.view updateState:newShadowView.state oldState:oldShadowView.state];
}
// `Finalize Updates` instruction
static void RNFinalizeUpdatesMountInstruction(
ShadowViewMutation const &mutation,
RNComponentViewUpdateMask mask,
RCTComponentViewRegistry *registry)
{
auto const &newShadowView = mutation.newChildShadowView;
auto const &componentViewDescriptor = [registry componentViewDescriptorWithTag:newShadowView.tag];
[componentViewDescriptor.view finalizeUpdates:mask];
}
// `Update` instruction
static void RNPerformMountInstructions(
ShadowViewMutationList const &mutations,
RCTComponentViewRegistry *registry,
RCTMountingTransactionObserverCoordinator &observerCoordinator,
SurfaceId surfaceId)
{
SystraceSection s("RNPerformMountInstructions");
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
for (auto const &mutation : mutations) {
switch (mutation.type) {
case ShadowViewMutation::Create: {
RNCreateMountInstruction(mutation, registry, observerCoordinator, surfaceId);
break;
}
case ShadowViewMutation::Delete: {
RNDeleteMountInstruction(mutation, registry, observerCoordinator, surfaceId);
break;
}
case ShadowViewMutation::Insert: {
RNUpdatePropsMountInstruction(mutation, registry);
RNUpdateEventEmitterMountInstruction(mutation, registry);
RNUpdateStateMountInstruction(mutation, registry);
RNUpdateLayoutMetricsMountInstruction(mutation, registry);
RNFinalizeUpdatesMountInstruction(mutation, RNComponentViewUpdateMaskAll, registry);
RNInsertMountInstruction(mutation, registry);
break;
}
case ShadowViewMutation::Remove: {
RNRemoveMountInstruction(mutation, registry);
break;
}
case ShadowViewMutation::Update: {
auto const &oldChildShadowView = mutation.oldChildShadowView;
auto const &newChildShadowView = mutation.newChildShadowView;
auto mask = RNComponentViewUpdateMask{};
if (oldChildShadowView.props != newChildShadowView.props) {
RNUpdatePropsMountInstruction(mutation, registry);
mask |= RNComponentViewUpdateMaskProps;
}
if (oldChildShadowView.eventEmitter != newChildShadowView.eventEmitter) {
RNUpdateEventEmitterMountInstruction(mutation, registry);
mask |= RNComponentViewUpdateMaskEventEmitter;
}
if (oldChildShadowView.state != newChildShadowView.state) {
RNUpdateStateMountInstruction(mutation, registry);
mask |= RNComponentViewUpdateMaskState;
}
if (oldChildShadowView.layoutMetrics != newChildShadowView.layoutMetrics) {
RNUpdateLayoutMetricsMountInstruction(mutation, registry);
mask |= RNComponentViewUpdateMaskLayoutMetrics;
}
if (mask != RNComponentViewUpdateMaskNone) {
RNFinalizeUpdatesMountInstruction(mutation, mask, registry);
}
break;
}
}
}
[CATransaction commit];
}
@implementation RCTMountingManager {
RCTMountingTransactionObserverCoordinator _observerCoordinator;
BOOL _transactionInFlight;
BOOL _followUpTransactionRequired;
}
- (instancetype)init
{
if (self = [super init]) {
_componentViewRegistry = [[RCTComponentViewRegistry alloc] init];
}
return self;
}
- (void)scheduleTransaction:(MountingCoordinator::Shared const &)mountingCoordinator
{
if (RCTIsMainQueue()) {
// Already on the proper thread, so:
// * No need to do a thread jump;
// * No need to do expensive copy of all mutations;
// * No need to allocate a block.
[self initiateTransaction:mountingCoordinator];
return;
}
auto mountingCoordinatorCopy = mountingCoordinator;
RCTExecuteOnMainQueue(^{
RCTAssertMainQueue();
[self initiateTransaction:mountingCoordinatorCopy];
});
}
- (void)dispatchCommand:(ReactTag)reactTag commandName:(NSString *)commandName args:(NSArray *)args
{
if (RCTIsMainQueue()) {
// Already on the proper thread, so:
// * No need to do a thread jump;
// * No need to allocate a block.
[self synchronouslyDispatchCommandOnUIThread:reactTag commandName:commandName args:args];
return;
}
RCTExecuteOnMainQueue(^{
RCTAssertMainQueue();
[self synchronouslyDispatchCommandOnUIThread:reactTag commandName:commandName args:args];
});
}
- (void)initiateTransaction:(MountingCoordinator::Shared const &)mountingCoordinator
{
SystraceSection s("-[RCTMountingManager initiateTransaction:]");
RCTAssertMainQueue();
if (_transactionInFlight) {
_followUpTransactionRequired = YES;
return;
}
do {
_followUpTransactionRequired = NO;
_transactionInFlight = YES;
[self performTransaction:mountingCoordinator];
_transactionInFlight = NO;
} while (_followUpTransactionRequired);
}
- (void)performTransaction:(MountingCoordinator::Shared const &)mountingCoordinator
{
SystraceSection s("-[RCTMountingManager performTransaction:]");
RCTAssertMainQueue();
auto transaction = mountingCoordinator->pullTransaction(DifferentiatorMode::Classic);
if (!transaction.has_value()) {
return;
}
auto surfaceId = transaction->getSurfaceId();
auto &mutations = transaction->getMutations();
if (mutations.size() == 0) {
return;
}
auto telemetry = transaction->getTelemetry();
auto number = transaction->getNumber();
[self.delegate mountingManager:self willMountComponentsWithRootTag:surfaceId];
_observerCoordinator.notifyObserversMountingTransactionWillMount({surfaceId, number, telemetry});
telemetry.willMount();
RNPerformMountInstructions(mutations, self.componentViewRegistry, _observerCoordinator, surfaceId);
telemetry.didMount();
_observerCoordinator.notifyObserversMountingTransactionDidMount({surfaceId, number, telemetry});
[self.delegate mountingManager:self didMountComponentsWithRootTag:surfaceId];
}
- (void)synchronouslyUpdateViewOnUIThread:(ReactTag)reactTag
changedProps:(NSDictionary *)props
componentDescriptor:(const ComponentDescriptor &)componentDescriptor
{
RCTAssertMainQueue();
UIView<RCTComponentViewProtocol> *componentView = [_componentViewRegistry findComponentViewWithTag:reactTag];
SharedProps oldProps = [componentView props];
SharedProps newProps = componentDescriptor.cloneProps(oldProps, RawProps(convertIdToFollyDynamic(props)));
[componentView updateProps:newProps oldProps:oldProps];
}
- (void)synchronouslyDispatchCommandOnUIThread:(ReactTag)reactTag
commandName:(NSString *)commandName
args:(NSArray *)args
{
RCTAssertMainQueue();
UIView<RCTComponentViewProtocol> *componentView = [_componentViewRegistry findComponentViewWithTag:reactTag];
[componentView handleCommand:commandName args:args];
}
@end

View File

@ -0,0 +1,37 @@
/*
* 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/RCTPrimitives.h>
NS_ASSUME_NONNULL_BEGIN
@class RCTMountingManager;
/**
* MountingManager's delegate.
*/
@protocol RCTMountingManagerDelegate <NSObject>
/*
* Called right *before* execution of mount items which affect a Surface with
* given `rootTag`.
* Always called on the main queue.
*/
- (void)mountingManager:(RCTMountingManager *)mountingManager willMountComponentsWithRootTag:(ReactTag)MountingManager;
/*
* Called right *after* execution of mount items which affect a Surface with
* given `rootTag`.
* Always called on the main queue.
*/
- (void)mountingManager:(RCTMountingManager *)mountingManager didMountComponentsWithRootTag:(ReactTag)rootTag;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,43 @@
/*
* 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 <React/RCTComponentViewDescriptor.h>
#import "RCTMountingTransactionObserverCoordinator.h"
#import <better/map.h>
#import <better/set.h>
#import <react/mounting/MountingTransactionMetadata.h>
class RCTMountingTransactionObserverCoordinator final {
public:
/*
* Registers (and unregisters) specified `componentViewDescriptor` in the
* registry of views that need to be notified. Does nothing if a particular
* `componentViewDescriptor` does not listen the events.
*/
void registerViewComponentDescriptor(
RCTComponentViewDescriptor const &componentViewDescriptor,
facebook::react::SurfaceId surfaceId);
void unregisterViewComponentDescriptor(
RCTComponentViewDescriptor const &componentViewDescriptor,
facebook::react::SurfaceId surfaceId);
/*
* To be called from `RCTMountingManager`.
*/
void notifyObserversMountingTransactionWillMount(
facebook::react::MountingTransactionMetadata const &metadata) const;
void notifyObserversMountingTransactionDidMount(
facebook::react::MountingTransactionMetadata const &metadata) const;
private:
facebook::better::map<
facebook::react::SurfaceId,
facebook::better::set<RCTComponentViewDescriptor>>
registry_;
};

View File

@ -0,0 +1,74 @@
/*
* 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 "RCTMountingTransactionObserverCoordinator.h"
#import "RCTMountingTransactionObserving.h"
using namespace facebook::react;
void RCTMountingTransactionObserverCoordinator::registerViewComponentDescriptor(
RCTComponentViewDescriptor const &componentViewDescriptor,
SurfaceId surfaceId)
{
if (!componentViewDescriptor.observesMountingTransactionWillMount &&
!componentViewDescriptor.observesMountingTransactionDidMount) {
return;
}
auto &surfaceRegistry = registry_[surfaceId];
assert(surfaceRegistry.count(componentViewDescriptor) == 0);
surfaceRegistry.insert(componentViewDescriptor);
}
void RCTMountingTransactionObserverCoordinator::unregisterViewComponentDescriptor(
RCTComponentViewDescriptor const &componentViewDescriptor,
SurfaceId surfaceId)
{
if (!componentViewDescriptor.observesMountingTransactionWillMount &&
!componentViewDescriptor.observesMountingTransactionDidMount) {
return;
}
auto &surfaceRegistry = registry_[surfaceId];
assert(surfaceRegistry.count(componentViewDescriptor) == 1);
surfaceRegistry.erase(componentViewDescriptor);
}
void RCTMountingTransactionObserverCoordinator::notifyObserversMountingTransactionWillMount(
MountingTransactionMetadata const &metadata) const
{
auto surfaceId = metadata.surfaceId;
auto surfaceRegistryIterator = registry_.find(surfaceId);
if (surfaceRegistryIterator == registry_.end()) {
return;
}
auto &surfaceRegistry = surfaceRegistryIterator->second;
for (auto const &componentViewDescriptor : surfaceRegistry) {
if (componentViewDescriptor.observesMountingTransactionWillMount) {
[(id<RCTMountingTransactionObserving>)componentViewDescriptor.view
mountingTransactionWillMountWithMetadata:metadata];
}
}
}
void RCTMountingTransactionObserverCoordinator::notifyObserversMountingTransactionDidMount(
MountingTransactionMetadata const &metadata) const
{
auto surfaceId = metadata.surfaceId;
auto surfaceRegistryIterator = registry_.find(surfaceId);
if (surfaceRegistryIterator == registry_.end()) {
return;
}
auto &surfaceRegistry = surfaceRegistryIterator->second;
for (auto const &componentViewDescriptor : surfaceRegistry) {
if (componentViewDescriptor.observesMountingTransactionDidMount) {
[(id<RCTMountingTransactionObserving>)componentViewDescriptor.view
mountingTransactionDidMountWithMetadata:metadata];
}
}
}

View File

@ -0,0 +1,64 @@
/*
* 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/mounting/MountingTransactionMetadata.h>
NS_ASSUME_NONNULL_BEGIN
/*
* # Achtung!
* Remember, with great power comes great responsibility.
* Observers of this protocol are being called several times on every single mount transaction. Any thoughtless or
* suboptimal implementation of this protocol will slow down the whole app. Please, be responsible.
*
* # Usecases
* React Native platform-specific mounting layer has limitations when it comes to notifying view components about
* (coming or just happened) changes in the view tree. Implementing that generically for all components would make
* everything way to slow. For instance, the mounting layer does not have dedicated APIs to notify some component that:
* - Some ancestor of the component was reparented;
* - Some descendant of the component was added, removed or reparented;
* - Some ancestor of the component got new layout metrics (which might affect the absolute position of the component);
* - The transaction which affected the component's children just finished.
*
* If some very specific component (e.g. a performance logger) needs to handle some of the similar use-cases, it might
* rely on this protocol.
*
* # How to use
* - Declare conformance to this protocol for the ComponentView class.
* - Implement methods *only* suitable for a particular use case. Do not implement all methods if it is not strictly
* required.
* - Alternatively, an observer can be registered explicitly via `RCTSurface`.
*
* # Implementation details
* The framework checks all registered view classes for conformance to the protocol and for a set of implemented
* methods, then it stores this information for future use. When a view got created, the framework checks the info
* associated with the class and adds the view object to the list of listeners of the particular events (if needed).
* When a view got destroyed, the framework removes the view from suitable collections.
*/
@protocol RCTMountingTransactionObserving <NSObject>
@optional
/*
* Called right before the fist mutation instruction is executed.
* Is not being called for a component view which is being mounted as part of the transaction (because the view is not
* registered as an observer yet).
*/
- (void)mountingTransactionWillMountWithMetadata:(facebook::react::MountingTransactionMetadata const &)metadata;
/*
* Called right after the last mutation instruction is executed.
* Is not being called for a component view which was being unmounted as part of the transaction (because the view is
* not registered as an observer already).
*/
- (void)mountingTransactionDidMountWithMetadata:(facebook::react::MountingTransactionMetadata const &)metadata;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,44 @@
/*
* 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/RCTComponentViewProtocol.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Default implementation of RCTComponentViewProtocol.
*/
@interface UIView (ComponentViewProtocol) <RCTComponentViewProtocol>
+ (std::vector<facebook::react::ComponentDescriptorProvider>)supplementalComponentDescriptorProviders;
- (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index;
- (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index;
- (void)updateProps:(facebook::react::Props::Shared const &)props
oldProps:(facebook::react::Props::Shared const &)oldProps;
- (void)updateEventEmitter:(facebook::react::EventEmitter::Shared const &)eventEmitter;
- (void)updateState:(facebook::react::State::Shared const &)state
oldState:(facebook::react::State::Shared const &)oldState;
- (void)updateLayoutMetrics:(facebook::react::LayoutMetrics const &)layoutMetrics
oldLayoutMetrics:(facebook::react::LayoutMetrics const &)oldLayoutMetrics;
- (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask;
- (void)prepareForRecycle;
- (facebook::react::SharedProps)props;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,118 @@
/*
* 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 "UIView+ComponentViewProtocol.h"
#import <React/RCTAssert.h>
#import <React/RCTLog.h>
#import <React/RCTUtils.h>
#import "RCTConversions.h"
using namespace facebook::react;
@implementation UIView (ComponentViewProtocol)
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
RCTAssert(NO, @"`-[RCTComponentViewProtocol componentDescriptorProvider]` must be implemented in a concrete class.");
return {};
}
+ (std::vector<facebook::react::ComponentDescriptorProvider>)supplementalComponentDescriptorProviders
{
return {};
}
- (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
{
[self insertSubview:childComponentView atIndex:index];
}
- (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
{
RCTAssert(childComponentView.superview == self, @"Attempt to unmount improperly mounted component view.");
[childComponentView removeFromSuperview];
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
// Default implementation does nothing.
}
- (void)updateEventEmitter:(EventEmitter::Shared const &)eventEmitter
{
// Default implementation does nothing.
}
- (void)updateState:(facebook::react::State::Shared const &)state
oldState:(facebook::react::State::Shared const &)oldState
{
// Default implementation does nothing.
}
- (void)handleCommand:(NSString *)commandName args:(NSArray *)args
{
// Default implementation does nothing.
}
- (void)updateLayoutMetrics:(LayoutMetrics const &)layoutMetrics
oldLayoutMetrics:(LayoutMetrics const &)oldLayoutMetrics
{
bool forceUpdate = oldLayoutMetrics == EmptyLayoutMetrics;
if (forceUpdate || (layoutMetrics.frame != oldLayoutMetrics.frame)) {
CGRect frame = RCTCGRectFromRect(layoutMetrics.frame);
if (!std::isfinite(frame.origin.x) || !std::isfinite(frame.origin.y) || !std::isfinite(frame.size.width) ||
!std::isfinite(frame.size.height)) {
// CALayer will crash if we pass NaN or Inf values.
// It's unclear how to detect this case on cross-platform manner holistically, so we have to do it on the mounting
// layer as well. NaN/Inf is a kinda valid result of some math operations. Even if we can (and should) detect (and
// report early) incorrect (NaN and Inf) values which come from JavaScript side, we sometimes cannot backtrace the
// sources of a calculation that produced an incorrect/useless result.
RCTLogWarn(
@"-[UIView(ComponentViewProtocol) updateLayoutMetrics:oldLayoutMetrics:]: Received invalid layout metrics (%@) for a view (%@).",
NSStringFromCGRect(frame),
self);
return;
}
// Note: Changing `frame` when `layer.transform` is not the `identity transform` is undefined behavior.
// Therefore, we must use `center` and `bounds`.
self.center = CGPoint{CGRectGetMidX(frame), CGRectGetMidY(frame)};
self.bounds = CGRect{CGPointZero, frame.size};
}
if (forceUpdate || (layoutMetrics.layoutDirection != oldLayoutMetrics.layoutDirection)) {
self.semanticContentAttribute = layoutMetrics.layoutDirection == LayoutDirection::RightToLeft
? UISemanticContentAttributeForceRightToLeft
: UISemanticContentAttributeForceLeftToRight;
}
if (forceUpdate || (layoutMetrics.displayType != oldLayoutMetrics.displayType)) {
self.hidden = layoutMetrics.displayType == DisplayType::None;
}
}
- (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask
{
// Default implementation does nothing.
}
- (void)prepareForRecycle
{
// Default implementation does nothing.
}
- (facebook::react::SharedProps)props
{
RCTAssert(NO, @"props access should be implemented by RCTViewComponentView.");
return nullptr;
}
@end