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