The New <Text> on iOS

Summary:
This is a complete rewrite of RCTText, the part of React Native which manages Text and TextInput components.

Key points:

* It's understandable now. It follows a simple architectural pattern, and it's easy to debug and iterate. Text flow layout is a first-class citizen in React Native layout system now, not just a wired special case. It also brings entirely new possibilities such as nested interleaving <Text> and <View> components.
* All <Text>-specific APIs were removed from UIManager and co (it's about ~16 public methods which were used exclusively only by <Text>).
* It relies on new Yoga measurement/cloning API and on-dirty handler. So, it removes built-in dirty propagation subsystem from RN completely.
* It caches string fragments properly and granularly on a per-node basis which makes updating text-containing components more performant.
* It does not instantiate UIView for virtual components which reduces memory utilization.
* It drastically improves <TextInput> capabilities (e.g. rich text inside single line <TextInput> is now supported).

Screenshots:
https://cl.ly/2j3r1V0L0324
https://cl.ly/3N2V3C3d3q3R

Reviewed By: mmmulani

Differential Revision: D6617326

fbshipit-source-id: 35d4d81b35c9870e9557d0211c0e934e6072a41e
This commit is contained in:
Valentin Shergin 2018-01-23 23:17:57 -08:00 committed by Facebook Github Bot
parent cd263a2cc7
commit 2716f53220
57 changed files with 2462 additions and 2115 deletions

View File

@ -0,0 +1,26 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <React/RCTShadowView.h>
#import "RCTTextAttributes.h"
NS_ASSUME_NONNULL_BEGIN
extern NSString *const RCTBaseTextShadowViewEmbeddedShadowViewAttributeName;
@interface RCTBaseTextShadowView : RCTShadowView
@property (nonatomic, strong) RCTTextAttributes *textAttributes;
- (NSAttributedString *)attributedTextWithBaseTextAttributes:(nullable RCTTextAttributes *)baseTextAttributes;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,125 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTBaseTextShadowView.h"
#import <React/RCTShadowView+Layout.h>
#import "RCTRawTextShadowView.h"
#import "RCTVirtualTextShadowView.h"
NSString *const RCTBaseTextShadowViewEmbeddedShadowViewAttributeName = @"RCTBaseTextShadowViewEmbeddedShadowViewAttributeName";
@implementation RCTBaseTextShadowView
{
NSAttributedString *_Nullable _cachedAttributedText;
RCTTextAttributes *_Nullable _cachedTextAttributes;
}
- (instancetype)init
{
if (self = [super init]) {
_textAttributes = [RCTTextAttributes new];
}
return self;
}
- (void)setReactTag:(NSNumber *)reactTag
{
[super setReactTag:reactTag];
_textAttributes.tag = reactTag;
}
#pragma mark - attributedString
- (NSAttributedString *)attributedTextWithBaseTextAttributes:(nullable RCTTextAttributes *)baseTextAttributes
{
RCTTextAttributes *textAttributes;
if (baseTextAttributes) {
textAttributes = [baseTextAttributes copy];
[textAttributes applyTextAttributes:self.textAttributes];
} else {
textAttributes = [self.textAttributes copy];
}
if (_cachedAttributedText && [_cachedTextAttributes isEqual:textAttributes]) {
return _cachedAttributedText;
}
NSMutableAttributedString *attributedText = [NSMutableAttributedString new];
[attributedText beginEditing];
for (RCTShadowView *shadowView in self.reactSubviews) {
// Special Case: RCTRawTextShadowView
if ([shadowView isKindOfClass:[RCTRawTextShadowView class]]) {
RCTRawTextShadowView *rawTextShadowView = (RCTRawTextShadowView *)shadowView;
NSString *text = rawTextShadowView.text;
if (text) {
NSAttributedString *rawTextAttributedString =
[[NSAttributedString alloc] initWithString:rawTextShadowView.text
attributes:textAttributes.effectiveTextAttributes];
[attributedText appendAttributedString:rawTextAttributedString];
}
continue;
}
// Special Case: RCTBaseTextShadowView
if ([shadowView isKindOfClass:[RCTBaseTextShadowView class]]) {
RCTBaseTextShadowView *baseTextShadowView = (RCTBaseTextShadowView *)shadowView;
NSAttributedString *baseTextAttributedString =
[baseTextShadowView attributedTextWithBaseTextAttributes:textAttributes];
[attributedText appendAttributedString:baseTextAttributedString];
continue;
}
// Generic Case: Any RCTShadowView
NSTextAttachment *attachment = [NSTextAttachment new];
NSMutableAttributedString *embeddedShadowViewAttributedString = [NSMutableAttributedString new];
[embeddedShadowViewAttributedString beginEditing];
[embeddedShadowViewAttributedString appendAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]];
[embeddedShadowViewAttributedString addAttribute:RCTBaseTextShadowViewEmbeddedShadowViewAttributeName
value:shadowView
range:(NSRange){0, embeddedShadowViewAttributedString.length}];
[embeddedShadowViewAttributedString endEditing];
[attributedText appendAttributedString:embeddedShadowViewAttributedString];
}
[attributedText endEditing];
[self clearLayout];
_cachedAttributedText = [attributedText copy];
_cachedTextAttributes = textAttributes;
return _cachedAttributedText;
}
- (void)dirtyLayout
{
[super dirtyLayout];
_cachedAttributedText = nil;
_cachedTextAttributes = nil;
}
- (void)didUpdateReactSubviews
{
[super didUpdateReactSubviews];
[self dirtyLayout];
}
- (void)didSetProps:(NSArray<NSString *> *)changedProps
{
[super didSetProps:changedProps];
[self dirtyLayout];
}
@end

View File

@ -7,8 +7,12 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
@protocol RCTFontAttributesDelegate <NSObject>
#import <React/RCTViewManager.h>
- (void)fontAttributesDidChangeWithFont:(UIFont *)font;
NS_ASSUME_NONNULL_BEGIN
@interface RCTBaseTextViewManager : RCTViewManager
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,57 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTBaseTextViewManager.h"
@implementation RCTBaseTextViewManager
RCT_EXPORT_MODULE(RCTBaseText)
- (UIView *)view
{
RCTAssert(NO, @"The `-[RCTBaseTextViewManager view]` property must be overridden in subclass.");
return nil;
}
- (RCTShadowView *)shadowView
{
RCTAssert(NO, @"The `-[RCTBaseTextViewManager shadowView]` property must be overridden in subclass.");
return nil;
}
#pragma mark - Text Attributes
// Color
RCT_REMAP_SHADOW_PROPERTY(color, textAttributes.foregroundColor, UIColor)
RCT_REMAP_SHADOW_PROPERTY(backgroundColor, textAttributes.backgroundColor, UIColor)
RCT_REMAP_SHADOW_PROPERTY(opacity, textAttributes.opacity, CGFloat)
// Font
RCT_REMAP_SHADOW_PROPERTY(fontFamily, textAttributes.fontFamily, NSString)
RCT_REMAP_SHADOW_PROPERTY(fontSize, textAttributes.fontSize, CGFloat)
RCT_REMAP_SHADOW_PROPERTY(fontWeight, textAttributes.fontWeight, NSString)
RCT_REMAP_SHADOW_PROPERTY(fontStyle, textAttributes.fontStyle, NSString)
RCT_REMAP_SHADOW_PROPERTY(fontVariant, textAttributes.fontVariant, NSArray)
RCT_REMAP_SHADOW_PROPERTY(allowFontScaling, textAttributes.allowFontScaling, BOOL)
RCT_REMAP_SHADOW_PROPERTY(letterSpacing, textAttributes.letterSpacing, CGFloat)
// Paragraph Styles
RCT_REMAP_SHADOW_PROPERTY(lineHeight, textAttributes.lineHeight, CGFloat)
RCT_REMAP_SHADOW_PROPERTY(textAlign, textAttributes.alignment, NSTextAlignment)
RCT_REMAP_SHADOW_PROPERTY(writingDirection, textAttributes.baseWritingDirection, NSWritingDirection)
// Decoration
RCT_REMAP_SHADOW_PROPERTY(textDecorationColor, textAttributes.textDecorationColor, UIColor)
RCT_REMAP_SHADOW_PROPERTY(textDecorationStyle, textAttributes.textDecorationStyle, NSUnderlineStyle)
RCT_REMAP_SHADOW_PROPERTY(textDecorationLine, textAttributes.textDecorationLine, RCTTextDecorationLineType)
// Shadow
RCT_REMAP_SHADOW_PROPERTY(textShadowOffset, textAttributes.textShadowOffset, CGSize)
RCT_REMAP_SHADOW_PROPERTY(textShadowRadius, textAttributes.textShadowRadius, CGFloat)
RCT_REMAP_SHADOW_PROPERTY(textShadowColor, textAttributes.textShadowColor, UIColor)
// Special
RCT_REMAP_SHADOW_PROPERTY(isHighlighted, textAttributes.isHighlighted, BOOL)
@end

View File

@ -9,9 +9,13 @@
#import <React/RCTConvert.h>
NS_ASSUME_NONNULL_BEGIN
@interface RCTConvert (Text)
+ (UITextAutocorrectionType)UITextAutocorrectionType:(id)json;
+ (UITextSpellCheckingType)UITextSpellCheckingType:(id)json;
+ (UITextAutocorrectionType)UITextAutocorrectionType:(nullable id)json;
+ (UITextSpellCheckingType)UITextSpellCheckingType:(nullable id)json;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,31 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
#import "RCTFontAttributesDelegate.h"
@class RCTAccessibilityManager;
@interface RCTFontAttributes : NSObject
@property (nonatomic, weak) id<RCTFontAttributesDelegate> delegate;
@property (readonly, nonatomic, strong) UIFont *font;
@property (nonatomic, assign) BOOL allowFontScaling;
@property (nonatomic, copy) NSString *fontFamily;
@property (nonatomic, strong) NSNumber *fontSize;
@property (nonatomic, assign) CGFloat fontSizeMultiplier;
@property (nonatomic, copy) NSString *fontStyle;
@property (nonatomic, copy) NSString *fontWeight;
- (instancetype)initWithAccessibilityManager:(RCTAccessibilityManager *)accessibilityManager;
@end

View File

@ -1,112 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTFontAttributes.h"
#import <React/RCTAccessibilityManager.h>
#import <React/RCTAssert.h>
#import <React/RCTFont.h>
#import <React/RCTLog.h>
@interface RCTFontAttributes ()
{
RCTAccessibilityManager *_accessibilityManager;
}
@property (nonatomic, strong) UIFont *font;
@end
@implementation RCTFontAttributes
- (instancetype)initWithAccessibilityManager:(RCTAccessibilityManager *)accessibilityManager
{
RCTAssertParam(accessibilityManager);
if (self = [super init]) {
_accessibilityManager = accessibilityManager;
_fontSizeMultiplier = _accessibilityManager.multiplier;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(contentSizeMultiplierDidChange)
name:RCTAccessibilityManagerDidUpdateMultiplierNotification
object:_accessibilityManager];
[self updateFont];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)contentSizeMultiplierDidChange
{
self.fontSizeMultiplier = _accessibilityManager.multiplier;
}
- (void)setAllowFontScaling:(BOOL)allowFontScaling
{
_allowFontScaling = allowFontScaling;
[self updateFont];
}
- (void)setFontFamily:(NSString *)fontFamily
{
_fontFamily = fontFamily;
[self updateFont];
}
- (void)setFontSize:(NSNumber *)fontSize
{
_fontSize = fontSize;
[self updateFont];
}
- (void)setFontSizeMultiplier:(CGFloat)fontSizeMultiplier
{
_fontSizeMultiplier = fontSizeMultiplier;
if (_fontSizeMultiplier == 0) {
RCTLogError(@"fontSizeMultiplier value must be > zero.");
_fontSizeMultiplier = 1.0;
}
[self updateFont];
}
- (void)setFontStyle:(NSString *)fontStyle
{
_fontStyle = fontStyle;
[self updateFont];
}
- (void)setFontWeight:(NSString *)fontWeight
{
_fontWeight = fontWeight;
[self updateFont];
}
- (void)updateFont
{
CGFloat scaleMultiplier = self.allowFontScaling ? self.fontSizeMultiplier : 1.0;
self.font = [RCTFont updateFont:nil
withFamily:self.fontFamily
size:self.fontSize
weight:self.fontWeight
style:self.fontStyle
variant:nil
scaleMultiplier:scaleMultiplier];
[self.delegate fontAttributesDidChangeWithFont:self.font];
}
@end

View File

@ -7,88 +7,102 @@
objects = {
/* Begin PBXBuildFile section */
5989E14E20018A6600EA444A /* RCTBaseTextInputViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5989E14B20018A2200EA444A /* RCTBaseTextInputViewManager.h */; };
5989E14F20018A7800EA444A /* RCTBaseTextInputViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5989E14B20018A2200EA444A /* RCTBaseTextInputViewManager.h */; };
59E604521FE9CAF100BD90C5 /* RCTTextShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E6042F1FE9CAF100BD90C5 /* RCTTextShadowView.m */; };
59E604531FE9CAF100BD90C5 /* RCTTextShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E6042F1FE9CAF100BD90C5 /* RCTTextShadowView.m */; };
59E604541FE9CAF100BD90C5 /* RCTTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E604311FE9CAF100BD90C5 /* RCTTextView.m */; };
59E604551FE9CAF100BD90C5 /* RCTTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E604311FE9CAF100BD90C5 /* RCTTextView.m */; };
59E604561FE9CAF100BD90C5 /* RCTTextViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E604331FE9CAF100BD90C5 /* RCTTextViewManager.m */; };
59E604571FE9CAF100BD90C5 /* RCTTextViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E604331FE9CAF100BD90C5 /* RCTTextViewManager.m */; };
59E604581FE9CAF100BD90C5 /* RCTRawTextShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E604341FE9CAF100BD90C5 /* RCTRawTextShadowView.m */; };
59E604591FE9CAF100BD90C5 /* RCTRawTextShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E604341FE9CAF100BD90C5 /* RCTRawTextShadowView.m */; };
59E6045A1FE9CAF100BD90C5 /* RCTRawTextViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E604361FE9CAF100BD90C5 /* RCTRawTextViewManager.m */; };
59E6045B1FE9CAF100BD90C5 /* RCTRawTextViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E604361FE9CAF100BD90C5 /* RCTRawTextViewManager.m */; };
59E6045C1FE9CAF100BD90C5 /* RCTMultilineTextInputShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E6043A1FE9CAF100BD90C5 /* RCTMultilineTextInputShadowView.m */; };
59E6045D1FE9CAF100BD90C5 /* RCTMultilineTextInputShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E6043A1FE9CAF100BD90C5 /* RCTMultilineTextInputShadowView.m */; };
59E6045E1FE9CAF100BD90C5 /* RCTMultilineTextInputView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E6043C1FE9CAF100BD90C5 /* RCTMultilineTextInputView.m */; };
59E6045F1FE9CAF100BD90C5 /* RCTMultilineTextInputView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E6043C1FE9CAF100BD90C5 /* RCTMultilineTextInputView.m */; };
59E604601FE9CAF100BD90C5 /* RCTMultilineTextInputViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E6043E1FE9CAF100BD90C5 /* RCTMultilineTextInputViewManager.m */; };
59E604611FE9CAF100BD90C5 /* RCTMultilineTextInputViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E6043E1FE9CAF100BD90C5 /* RCTMultilineTextInputViewManager.m */; };
59E604621FE9CAF100BD90C5 /* RCTUITextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E604401FE9CAF100BD90C5 /* RCTUITextView.m */; };
59E604631FE9CAF100BD90C5 /* RCTUITextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E604401FE9CAF100BD90C5 /* RCTUITextView.m */; };
59E604641FE9CAF100BD90C5 /* RCTBackedTextInputDelegateAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E604431FE9CAF100BD90C5 /* RCTBackedTextInputDelegateAdapter.m */; };
59E604651FE9CAF100BD90C5 /* RCTBackedTextInputDelegateAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E604431FE9CAF100BD90C5 /* RCTBackedTextInputDelegateAdapter.m */; };
59E604661FE9CAF100BD90C5 /* RCTBaseTextInputView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E604461FE9CAF100BD90C5 /* RCTBaseTextInputView.m */; };
59E604671FE9CAF100BD90C5 /* RCTBaseTextInputView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E604461FE9CAF100BD90C5 /* RCTBaseTextInputView.m */; };
59E604681FE9CAF100BD90C5 /* RCTTextSelection.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E604481FE9CAF100BD90C5 /* RCTTextSelection.m */; };
59E604691FE9CAF100BD90C5 /* RCTTextSelection.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E604481FE9CAF100BD90C5 /* RCTTextSelection.m */; };
59E6046A1FE9CAF100BD90C5 /* RCTSinglelineTextInputShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E6044B1FE9CAF100BD90C5 /* RCTSinglelineTextInputShadowView.m */; };
59E6046B1FE9CAF100BD90C5 /* RCTSinglelineTextInputShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E6044B1FE9CAF100BD90C5 /* RCTSinglelineTextInputShadowView.m */; };
59E6046C1FE9CAF100BD90C5 /* RCTSinglelineTextInputView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E6044D1FE9CAF100BD90C5 /* RCTSinglelineTextInputView.m */; };
59E6046D1FE9CAF100BD90C5 /* RCTSinglelineTextInputView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E6044D1FE9CAF100BD90C5 /* RCTSinglelineTextInputView.m */; };
59E6046E1FE9CAF100BD90C5 /* RCTSinglelineTextInputViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E6044F1FE9CAF100BD90C5 /* RCTSinglelineTextInputViewManager.m */; };
59E6046F1FE9CAF100BD90C5 /* RCTSinglelineTextInputViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E6044F1FE9CAF100BD90C5 /* RCTSinglelineTextInputViewManager.m */; };
59E604701FE9CAF100BD90C5 /* RCTUITextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E604511FE9CAF100BD90C5 /* RCTUITextField.m */; };
59E604711FE9CAF100BD90C5 /* RCTUITextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E604511FE9CAF100BD90C5 /* RCTUITextField.m */; };
59E604721FE9CB3F00BD90C5 /* RCTConvert+Text.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = AF3225F71DE5574F00D3E7E7 /* RCTConvert+Text.h */; };
59E604731FE9CB3F00BD90C5 /* RCTFontAttributes.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = A85C82981F742AA20036C019 /* RCTFontAttributes.h */; };
59E604741FE9CB3F00BD90C5 /* RCTFontAttributesDelegate.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = A85C82BA1F742D8F0036C019 /* RCTFontAttributesDelegate.h */; };
59E604751FE9CB3F00BD90C5 /* RCTRawTextShadowView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E6042C1FE9CAF100BD90C5 /* RCTRawTextShadowView.h */; };
59E604761FE9CB3F00BD90C5 /* RCTRawTextViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E604351FE9CAF100BD90C5 /* RCTRawTextViewManager.h */; };
59E604771FE9CB3F00BD90C5 /* RCTTextShadowView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E6042E1FE9CAF100BD90C5 /* RCTTextShadowView.h */; };
59E604781FE9CB3F00BD90C5 /* RCTTextView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E604301FE9CAF100BD90C5 /* RCTTextView.h */; };
59E604791FE9CB3F00BD90C5 /* RCTTextViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E604321FE9CAF100BD90C5 /* RCTTextViewManager.h */; };
59E6047A1FE9CB3F00BD90C5 /* RCTMultilineTextInputShadowView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E604391FE9CAF100BD90C5 /* RCTMultilineTextInputShadowView.h */; };
59E6047B1FE9CB3F00BD90C5 /* RCTMultilineTextInputView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E6043B1FE9CAF100BD90C5 /* RCTMultilineTextInputView.h */; };
59E6047C1FE9CB3F00BD90C5 /* RCTMultilineTextInputViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E6043D1FE9CAF100BD90C5 /* RCTMultilineTextInputViewManager.h */; };
59E6047D1FE9CB3F00BD90C5 /* RCTUITextView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E6043F1FE9CAF100BD90C5 /* RCTUITextView.h */; };
59E6047E1FE9CB3F00BD90C5 /* RCTBackedTextInputDelegate.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E604411FE9CAF100BD90C5 /* RCTBackedTextInputDelegate.h */; };
59E6047F1FE9CB3F00BD90C5 /* RCTBackedTextInputDelegateAdapter.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E604421FE9CAF100BD90C5 /* RCTBackedTextInputDelegateAdapter.h */; };
59E604801FE9CB3F00BD90C5 /* RCTBackedTextInputViewProtocol.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E604441FE9CAF100BD90C5 /* RCTBackedTextInputViewProtocol.h */; };
59E604811FE9CB3F00BD90C5 /* RCTBaseTextInputView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E604451FE9CAF100BD90C5 /* RCTBaseTextInputView.h */; };
59E604821FE9CB3F00BD90C5 /* RCTTextSelection.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E604471FE9CAF100BD90C5 /* RCTTextSelection.h */; };
59E604831FE9CB3F00BD90C5 /* RCTSinglelineTextInputShadowView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E6044A1FE9CAF100BD90C5 /* RCTSinglelineTextInputShadowView.h */; };
59E604841FE9CB3F00BD90C5 /* RCTSinglelineTextInputView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E6044C1FE9CAF100BD90C5 /* RCTSinglelineTextInputView.h */; };
59E604851FE9CB3F00BD90C5 /* RCTSinglelineTextInputViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E6044E1FE9CAF100BD90C5 /* RCTSinglelineTextInputViewManager.h */; };
59E604861FE9CB3F00BD90C5 /* RCTUITextField.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E604501FE9CAF100BD90C5 /* RCTUITextField.h */; };
59E604871FE9CB4A00BD90C5 /* RCTConvert+Text.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = AF3225F71DE5574F00D3E7E7 /* RCTConvert+Text.h */; };
59E604881FE9CB4A00BD90C5 /* RCTFontAttributes.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = A85C82981F742AA20036C019 /* RCTFontAttributes.h */; };
59E604891FE9CB4A00BD90C5 /* RCTFontAttributesDelegate.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = A85C82BA1F742D8F0036C019 /* RCTFontAttributesDelegate.h */; };
59E6048A1FE9CB4A00BD90C5 /* RCTRawTextShadowView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E6042C1FE9CAF100BD90C5 /* RCTRawTextShadowView.h */; };
59E6048B1FE9CB4A00BD90C5 /* RCTRawTextViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E604351FE9CAF100BD90C5 /* RCTRawTextViewManager.h */; };
59E6048C1FE9CB4A00BD90C5 /* RCTTextShadowView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E6042E1FE9CAF100BD90C5 /* RCTTextShadowView.h */; };
59E6048D1FE9CB4A00BD90C5 /* RCTTextView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E604301FE9CAF100BD90C5 /* RCTTextView.h */; };
59E6048E1FE9CB4A00BD90C5 /* RCTTextViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E604321FE9CAF100BD90C5 /* RCTTextViewManager.h */; };
59E6048F1FE9CB4A00BD90C5 /* RCTMultilineTextInputShadowView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E604391FE9CAF100BD90C5 /* RCTMultilineTextInputShadowView.h */; };
59E604901FE9CB4A00BD90C5 /* RCTMultilineTextInputView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E6043B1FE9CAF100BD90C5 /* RCTMultilineTextInputView.h */; };
59E604911FE9CB4A00BD90C5 /* RCTMultilineTextInputViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E6043D1FE9CAF100BD90C5 /* RCTMultilineTextInputViewManager.h */; };
59E604921FE9CB4A00BD90C5 /* RCTUITextView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E6043F1FE9CAF100BD90C5 /* RCTUITextView.h */; };
59E604931FE9CB4A00BD90C5 /* RCTBackedTextInputDelegate.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E604411FE9CAF100BD90C5 /* RCTBackedTextInputDelegate.h */; };
59E604941FE9CB4A00BD90C5 /* RCTBackedTextInputDelegateAdapter.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E604421FE9CAF100BD90C5 /* RCTBackedTextInputDelegateAdapter.h */; };
59E604951FE9CB4A00BD90C5 /* RCTBackedTextInputViewProtocol.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E604441FE9CAF100BD90C5 /* RCTBackedTextInputViewProtocol.h */; };
59E604961FE9CB4A00BD90C5 /* RCTBaseTextInputView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E604451FE9CAF100BD90C5 /* RCTBaseTextInputView.h */; };
59E604971FE9CB4A00BD90C5 /* RCTTextSelection.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E604471FE9CAF100BD90C5 /* RCTTextSelection.h */; };
59E604981FE9CB4A00BD90C5 /* RCTSinglelineTextInputShadowView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E6044A1FE9CAF100BD90C5 /* RCTSinglelineTextInputShadowView.h */; };
59E604991FE9CB4A00BD90C5 /* RCTSinglelineTextInputView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E6044C1FE9CAF100BD90C5 /* RCTSinglelineTextInputView.h */; };
59E6049A1FE9CB4A00BD90C5 /* RCTSinglelineTextInputViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E6044E1FE9CAF100BD90C5 /* RCTSinglelineTextInputViewManager.h */; };
59E6049B1FE9CB4A00BD90C5 /* RCTUITextField.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 59E604501FE9CAF100BD90C5 /* RCTUITextField.h */; };
59E8C5CC1F8833D100204F5E /* RCTFontAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = A85C82991F742AA20036C019 /* RCTFontAttributes.m */; };
657980E82008B2EE00E908AF /* RCTBaseTextInputViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 657980E72008B2EE00E908AF /* RCTBaseTextInputViewManager.m */; };
657980E92008B2FA00E908AF /* RCTBaseTextInputViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 657980E72008B2EE00E908AF /* RCTBaseTextInputViewManager.m */; };
A85C829A1F742AA20036C019 /* RCTFontAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = A85C82991F742AA20036C019 /* RCTFontAttributes.m */; };
AF3225F91DE5574F00D3E7E7 /* RCTConvert+Text.m in Sources */ = {isa = PBXBuildFile; fileRef = AF3225F81DE5574F00D3E7E7 /* RCTConvert+Text.m */; };
AF3225FA1DE5574F00D3E7E7 /* RCTConvert+Text.m in Sources */ = {isa = PBXBuildFile; fileRef = AF3225F81DE5574F00D3E7E7 /* RCTConvert+Text.m */; };
5956B130200FEBAA008D9D16 /* RCTRawTextShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B0FD200FEBA9008D9D16 /* RCTRawTextShadowView.m */; };
5956B131200FEBAA008D9D16 /* RCTRawTextViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B0FE200FEBA9008D9D16 /* RCTRawTextViewManager.m */; };
5956B132200FEBAA008D9D16 /* RCTSinglelineTextInputView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B101200FEBA9008D9D16 /* RCTSinglelineTextInputView.m */; };
5956B133200FEBAA008D9D16 /* RCTUITextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B103200FEBA9008D9D16 /* RCTUITextField.m */; };
5956B134200FEBAA008D9D16 /* RCTSinglelineTextInputViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B106200FEBA9008D9D16 /* RCTSinglelineTextInputViewManager.m */; };
5956B135200FEBAA008D9D16 /* RCTBaseTextInputView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B10A200FEBA9008D9D16 /* RCTBaseTextInputView.m */; };
5956B136200FEBAA008D9D16 /* RCTBackedTextInputDelegateAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B10D200FEBA9008D9D16 /* RCTBackedTextInputDelegateAdapter.m */; };
5956B137200FEBAA008D9D16 /* RCTBaseTextInputShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B10F200FEBA9008D9D16 /* RCTBaseTextInputShadowView.m */; };
5956B138200FEBAA008D9D16 /* RCTTextSelection.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B110200FEBA9008D9D16 /* RCTTextSelection.m */; };
5956B139200FEBAA008D9D16 /* RCTBaseTextInputViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B111200FEBA9008D9D16 /* RCTBaseTextInputViewManager.m */; };
5956B13A200FEBAA008D9D16 /* RCTMultilineTextInputView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B115200FEBA9008D9D16 /* RCTMultilineTextInputView.m */; };
5956B13B200FEBAA008D9D16 /* RCTMultilineTextInputViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B118200FEBA9008D9D16 /* RCTMultilineTextInputViewManager.m */; };
5956B13C200FEBAA008D9D16 /* RCTUITextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B119200FEBA9008D9D16 /* RCTUITextView.m */; };
5956B13D200FEBAA008D9D16 /* RCTBaseTextShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B11E200FEBA9008D9D16 /* RCTBaseTextShadowView.m */; };
5956B13E200FEBAA008D9D16 /* RCTBaseTextViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B11F200FEBA9008D9D16 /* RCTBaseTextViewManager.m */; };
5956B13F200FEBAA008D9D16 /* RCTTextAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B120200FEBA9008D9D16 /* RCTTextAttributes.m */; };
5956B140200FEBAA008D9D16 /* RCTTextShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B122200FEBAA008D9D16 /* RCTTextShadowView.m */; };
5956B141200FEBAA008D9D16 /* NSTextStorage+FontScaling.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B125200FEBAA008D9D16 /* NSTextStorage+FontScaling.m */; };
5956B142200FEBAA008D9D16 /* RCTTextViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B127200FEBAA008D9D16 /* RCTTextViewManager.m */; };
5956B143200FEBAA008D9D16 /* RCTTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B128200FEBAA008D9D16 /* RCTTextView.m */; };
5956B144200FEBAA008D9D16 /* RCTVirtualTextViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B12B200FEBAA008D9D16 /* RCTVirtualTextViewManager.m */; };
5956B145200FEBAA008D9D16 /* RCTVirtualTextShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B12E200FEBAA008D9D16 /* RCTVirtualTextShadowView.m */; };
5956B146200FEBAA008D9D16 /* RCTConvert+Text.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B12F200FEBAA008D9D16 /* RCTConvert+Text.m */; };
5956B160200FF324008D9D16 /* RCTBaseTextShadowView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B11D200FEBA9008D9D16 /* RCTBaseTextShadowView.h */; };
5956B161200FF324008D9D16 /* RCTBaseTextViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B11C200FEBA9008D9D16 /* RCTBaseTextViewManager.h */; };
5956B162200FF324008D9D16 /* RCTRawTextShadowView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B0FC200FEBA9008D9D16 /* RCTRawTextShadowView.h */; };
5956B163200FF324008D9D16 /* RCTRawTextViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B0FB200FEBA9008D9D16 /* RCTRawTextViewManager.h */; };
5956B164200FF324008D9D16 /* RCTConvert+Text.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B0F9200FEBA9008D9D16 /* RCTConvert+Text.h */; };
5956B165200FF324008D9D16 /* RCTTextAttributes.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B11A200FEBA9008D9D16 /* RCTTextAttributes.h */; };
5956B166200FF324008D9D16 /* NSTextStorage+FontScaling.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B129200FEBAA008D9D16 /* NSTextStorage+FontScaling.h */; };
5956B167200FF324008D9D16 /* RCTTextShadowView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B126200FEBAA008D9D16 /* RCTTextShadowView.h */; };
5956B168200FF324008D9D16 /* RCTTextView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B123200FEBAA008D9D16 /* RCTTextView.h */; };
5956B169200FF324008D9D16 /* RCTTextViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B124200FEBAA008D9D16 /* RCTTextViewManager.h */; };
5956B16A200FF324008D9D16 /* RCTMultilineTextInputView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B117200FEBA9008D9D16 /* RCTMultilineTextInputView.h */; };
5956B16B200FF324008D9D16 /* RCTMultilineTextInputViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B114200FEBA9008D9D16 /* RCTMultilineTextInputViewManager.h */; };
5956B16C200FF324008D9D16 /* RCTUITextView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B116200FEBA9008D9D16 /* RCTUITextView.h */; };
5956B16D200FF324008D9D16 /* RCTBackedTextInputDelegate.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B10C200FEBA9008D9D16 /* RCTBackedTextInputDelegate.h */; };
5956B16E200FF324008D9D16 /* RCTBackedTextInputDelegateAdapter.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B107200FEBA9008D9D16 /* RCTBackedTextInputDelegateAdapter.h */; };
5956B16F200FF324008D9D16 /* RCTBackedTextInputViewProtocol.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B112200FEBA9008D9D16 /* RCTBackedTextInputViewProtocol.h */; };
5956B170200FF324008D9D16 /* RCTBaseTextInputShadowView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B109200FEBA9008D9D16 /* RCTBaseTextInputShadowView.h */; };
5956B171200FF324008D9D16 /* RCTBaseTextInputView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B10E200FEBA9008D9D16 /* RCTBaseTextInputView.h */; };
5956B172200FF324008D9D16 /* RCTBaseTextInputViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B10B200FEBA9008D9D16 /* RCTBaseTextInputViewManager.h */; };
5956B173200FF324008D9D16 /* RCTTextSelection.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B108200FEBA9008D9D16 /* RCTTextSelection.h */; };
5956B174200FF324008D9D16 /* RCTSinglelineTextInputView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B104200FEBA9008D9D16 /* RCTSinglelineTextInputView.h */; };
5956B175200FF324008D9D16 /* RCTSinglelineTextInputViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B102200FEBA9008D9D16 /* RCTSinglelineTextInputViewManager.h */; };
5956B176200FF324008D9D16 /* RCTUITextField.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B105200FEBA9008D9D16 /* RCTUITextField.h */; };
5956B177200FF324008D9D16 /* RCTVirtualTextShadowView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B12C200FEBAA008D9D16 /* RCTVirtualTextShadowView.h */; };
5956B178200FF324008D9D16 /* RCTVirtualTextViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B12D200FEBAA008D9D16 /* RCTVirtualTextViewManager.h */; };
5956B179200FF338008D9D16 /* RCTBaseTextShadowView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B11D200FEBA9008D9D16 /* RCTBaseTextShadowView.h */; };
5956B17A200FF338008D9D16 /* RCTBaseTextViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B11C200FEBA9008D9D16 /* RCTBaseTextViewManager.h */; };
5956B17B200FF338008D9D16 /* RCTRawTextShadowView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B0FC200FEBA9008D9D16 /* RCTRawTextShadowView.h */; };
5956B17C200FF338008D9D16 /* RCTRawTextViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B0FB200FEBA9008D9D16 /* RCTRawTextViewManager.h */; };
5956B17D200FF338008D9D16 /* RCTConvert+Text.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B0F9200FEBA9008D9D16 /* RCTConvert+Text.h */; };
5956B17E200FF338008D9D16 /* RCTTextAttributes.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B11A200FEBA9008D9D16 /* RCTTextAttributes.h */; };
5956B17F200FF338008D9D16 /* NSTextStorage+FontScaling.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B129200FEBAA008D9D16 /* NSTextStorage+FontScaling.h */; };
5956B180200FF338008D9D16 /* RCTTextShadowView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B126200FEBAA008D9D16 /* RCTTextShadowView.h */; };
5956B181200FF338008D9D16 /* RCTTextView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B123200FEBAA008D9D16 /* RCTTextView.h */; };
5956B182200FF338008D9D16 /* RCTTextViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B124200FEBAA008D9D16 /* RCTTextViewManager.h */; };
5956B183200FF338008D9D16 /* RCTMultilineTextInputView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B117200FEBA9008D9D16 /* RCTMultilineTextInputView.h */; };
5956B184200FF338008D9D16 /* RCTMultilineTextInputViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B114200FEBA9008D9D16 /* RCTMultilineTextInputViewManager.h */; };
5956B185200FF338008D9D16 /* RCTUITextView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B116200FEBA9008D9D16 /* RCTUITextView.h */; };
5956B186200FF338008D9D16 /* RCTBackedTextInputDelegate.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B10C200FEBA9008D9D16 /* RCTBackedTextInputDelegate.h */; };
5956B187200FF338008D9D16 /* RCTBackedTextInputDelegateAdapter.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B107200FEBA9008D9D16 /* RCTBackedTextInputDelegateAdapter.h */; };
5956B188200FF338008D9D16 /* RCTBackedTextInputViewProtocol.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B112200FEBA9008D9D16 /* RCTBackedTextInputViewProtocol.h */; };
5956B189200FF338008D9D16 /* RCTBaseTextInputShadowView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B109200FEBA9008D9D16 /* RCTBaseTextInputShadowView.h */; };
5956B18A200FF338008D9D16 /* RCTBaseTextInputView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B10E200FEBA9008D9D16 /* RCTBaseTextInputView.h */; };
5956B18B200FF338008D9D16 /* RCTBaseTextInputViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B10B200FEBA9008D9D16 /* RCTBaseTextInputViewManager.h */; };
5956B18C200FF338008D9D16 /* RCTTextSelection.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B108200FEBA9008D9D16 /* RCTTextSelection.h */; };
5956B18D200FF338008D9D16 /* RCTSinglelineTextInputView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B104200FEBA9008D9D16 /* RCTSinglelineTextInputView.h */; };
5956B18E200FF338008D9D16 /* RCTSinglelineTextInputViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B102200FEBA9008D9D16 /* RCTSinglelineTextInputViewManager.h */; };
5956B18F200FF338008D9D16 /* RCTUITextField.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B105200FEBA9008D9D16 /* RCTUITextField.h */; };
5956B190200FF338008D9D16 /* RCTVirtualTextShadowView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B12C200FEBAA008D9D16 /* RCTVirtualTextShadowView.h */; };
5956B191200FF338008D9D16 /* RCTVirtualTextViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5956B12D200FEBAA008D9D16 /* RCTVirtualTextViewManager.h */; };
5956B192200FF35C008D9D16 /* RCTBaseTextShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B11E200FEBA9008D9D16 /* RCTBaseTextShadowView.m */; };
5956B193200FF35C008D9D16 /* RCTBaseTextViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B11F200FEBA9008D9D16 /* RCTBaseTextViewManager.m */; };
5956B194200FF35C008D9D16 /* RCTRawTextShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B0FD200FEBA9008D9D16 /* RCTRawTextShadowView.m */; };
5956B195200FF35C008D9D16 /* RCTRawTextViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B0FE200FEBA9008D9D16 /* RCTRawTextViewManager.m */; };
5956B196200FF35C008D9D16 /* RCTConvert+Text.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B12F200FEBAA008D9D16 /* RCTConvert+Text.m */; };
5956B197200FF35C008D9D16 /* RCTTextAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B120200FEBA9008D9D16 /* RCTTextAttributes.m */; };
5956B198200FF35C008D9D16 /* NSTextStorage+FontScaling.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B125200FEBAA008D9D16 /* NSTextStorage+FontScaling.m */; };
5956B199200FF35C008D9D16 /* RCTTextShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B122200FEBAA008D9D16 /* RCTTextShadowView.m */; };
5956B19A200FF35C008D9D16 /* RCTTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B128200FEBAA008D9D16 /* RCTTextView.m */; };
5956B19B200FF35C008D9D16 /* RCTTextViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B127200FEBAA008D9D16 /* RCTTextViewManager.m */; };
5956B19C200FF35C008D9D16 /* RCTMultilineTextInputView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B115200FEBA9008D9D16 /* RCTMultilineTextInputView.m */; };
5956B19D200FF35C008D9D16 /* RCTMultilineTextInputViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B118200FEBA9008D9D16 /* RCTMultilineTextInputViewManager.m */; };
5956B19E200FF35C008D9D16 /* RCTUITextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B119200FEBA9008D9D16 /* RCTUITextView.m */; };
5956B19F200FF35C008D9D16 /* RCTBackedTextInputDelegateAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B10D200FEBA9008D9D16 /* RCTBackedTextInputDelegateAdapter.m */; };
5956B1A0200FF35C008D9D16 /* RCTBaseTextInputShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B10F200FEBA9008D9D16 /* RCTBaseTextInputShadowView.m */; };
5956B1A1200FF35C008D9D16 /* RCTBaseTextInputView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B10A200FEBA9008D9D16 /* RCTBaseTextInputView.m */; };
5956B1A2200FF35C008D9D16 /* RCTBaseTextInputViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B111200FEBA9008D9D16 /* RCTBaseTextInputViewManager.m */; };
5956B1A3200FF35C008D9D16 /* RCTTextSelection.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B110200FEBA9008D9D16 /* RCTTextSelection.m */; };
5956B1A4200FF35C008D9D16 /* RCTSinglelineTextInputView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B101200FEBA9008D9D16 /* RCTSinglelineTextInputView.m */; };
5956B1A5200FF35C008D9D16 /* RCTSinglelineTextInputViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B106200FEBA9008D9D16 /* RCTSinglelineTextInputViewManager.m */; };
5956B1A6200FF35C008D9D16 /* RCTUITextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B103200FEBA9008D9D16 /* RCTUITextField.m */; };
5956B1A7200FF35C008D9D16 /* RCTVirtualTextShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B12E200FEBAA008D9D16 /* RCTVirtualTextShadowView.m */; };
5956B1A8200FF35C008D9D16 /* RCTVirtualTextViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B12B200FEBAA008D9D16 /* RCTVirtualTextViewManager.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@ -98,28 +112,31 @@
dstPath = include/RCTText;
dstSubfolderSpec = 16;
files = (
5989E14E20018A6600EA444A /* RCTBaseTextInputViewManager.h in Copy Headers */,
59E604871FE9CB4A00BD90C5 /* RCTConvert+Text.h in Copy Headers */,
59E604881FE9CB4A00BD90C5 /* RCTFontAttributes.h in Copy Headers */,
59E604891FE9CB4A00BD90C5 /* RCTFontAttributesDelegate.h in Copy Headers */,
59E6048A1FE9CB4A00BD90C5 /* RCTRawTextShadowView.h in Copy Headers */,
59E6048B1FE9CB4A00BD90C5 /* RCTRawTextViewManager.h in Copy Headers */,
59E6048C1FE9CB4A00BD90C5 /* RCTTextShadowView.h in Copy Headers */,
59E6048D1FE9CB4A00BD90C5 /* RCTTextView.h in Copy Headers */,
59E6048E1FE9CB4A00BD90C5 /* RCTTextViewManager.h in Copy Headers */,
59E6048F1FE9CB4A00BD90C5 /* RCTMultilineTextInputShadowView.h in Copy Headers */,
59E604901FE9CB4A00BD90C5 /* RCTMultilineTextInputView.h in Copy Headers */,
59E604911FE9CB4A00BD90C5 /* RCTMultilineTextInputViewManager.h in Copy Headers */,
59E604921FE9CB4A00BD90C5 /* RCTUITextView.h in Copy Headers */,
59E604931FE9CB4A00BD90C5 /* RCTBackedTextInputDelegate.h in Copy Headers */,
59E604941FE9CB4A00BD90C5 /* RCTBackedTextInputDelegateAdapter.h in Copy Headers */,
59E604951FE9CB4A00BD90C5 /* RCTBackedTextInputViewProtocol.h in Copy Headers */,
59E604961FE9CB4A00BD90C5 /* RCTBaseTextInputView.h in Copy Headers */,
59E604971FE9CB4A00BD90C5 /* RCTTextSelection.h in Copy Headers */,
59E604981FE9CB4A00BD90C5 /* RCTSinglelineTextInputShadowView.h in Copy Headers */,
59E604991FE9CB4A00BD90C5 /* RCTSinglelineTextInputView.h in Copy Headers */,
59E6049A1FE9CB4A00BD90C5 /* RCTSinglelineTextInputViewManager.h in Copy Headers */,
59E6049B1FE9CB4A00BD90C5 /* RCTUITextField.h in Copy Headers */,
5956B160200FF324008D9D16 /* RCTBaseTextShadowView.h in Copy Headers */,
5956B161200FF324008D9D16 /* RCTBaseTextViewManager.h in Copy Headers */,
5956B162200FF324008D9D16 /* RCTRawTextShadowView.h in Copy Headers */,
5956B163200FF324008D9D16 /* RCTRawTextViewManager.h in Copy Headers */,
5956B164200FF324008D9D16 /* RCTConvert+Text.h in Copy Headers */,
5956B165200FF324008D9D16 /* RCTTextAttributes.h in Copy Headers */,
5956B166200FF324008D9D16 /* NSTextStorage+FontScaling.h in Copy Headers */,
5956B167200FF324008D9D16 /* RCTTextShadowView.h in Copy Headers */,
5956B168200FF324008D9D16 /* RCTTextView.h in Copy Headers */,
5956B169200FF324008D9D16 /* RCTTextViewManager.h in Copy Headers */,
5956B16A200FF324008D9D16 /* RCTMultilineTextInputView.h in Copy Headers */,
5956B16B200FF324008D9D16 /* RCTMultilineTextInputViewManager.h in Copy Headers */,
5956B16C200FF324008D9D16 /* RCTUITextView.h in Copy Headers */,
5956B16D200FF324008D9D16 /* RCTBackedTextInputDelegate.h in Copy Headers */,
5956B16E200FF324008D9D16 /* RCTBackedTextInputDelegateAdapter.h in Copy Headers */,
5956B16F200FF324008D9D16 /* RCTBackedTextInputViewProtocol.h in Copy Headers */,
5956B170200FF324008D9D16 /* RCTBaseTextInputShadowView.h in Copy Headers */,
5956B171200FF324008D9D16 /* RCTBaseTextInputView.h in Copy Headers */,
5956B172200FF324008D9D16 /* RCTBaseTextInputViewManager.h in Copy Headers */,
5956B173200FF324008D9D16 /* RCTTextSelection.h in Copy Headers */,
5956B174200FF324008D9D16 /* RCTSinglelineTextInputView.h in Copy Headers */,
5956B175200FF324008D9D16 /* RCTSinglelineTextInputViewManager.h in Copy Headers */,
5956B176200FF324008D9D16 /* RCTUITextField.h in Copy Headers */,
5956B177200FF324008D9D16 /* RCTVirtualTextShadowView.h in Copy Headers */,
5956B178200FF324008D9D16 /* RCTVirtualTextViewManager.h in Copy Headers */,
);
name = "Copy Headers";
runOnlyForDeploymentPostprocessing = 0;
@ -130,28 +147,31 @@
dstPath = include/RCTText;
dstSubfolderSpec = 16;
files = (
5989E14F20018A7800EA444A /* RCTBaseTextInputViewManager.h in Copy Headers */,
59E604721FE9CB3F00BD90C5 /* RCTConvert+Text.h in Copy Headers */,
59E604731FE9CB3F00BD90C5 /* RCTFontAttributes.h in Copy Headers */,
59E604741FE9CB3F00BD90C5 /* RCTFontAttributesDelegate.h in Copy Headers */,
59E604751FE9CB3F00BD90C5 /* RCTRawTextShadowView.h in Copy Headers */,
59E604761FE9CB3F00BD90C5 /* RCTRawTextViewManager.h in Copy Headers */,
59E604771FE9CB3F00BD90C5 /* RCTTextShadowView.h in Copy Headers */,
59E604781FE9CB3F00BD90C5 /* RCTTextView.h in Copy Headers */,
59E604791FE9CB3F00BD90C5 /* RCTTextViewManager.h in Copy Headers */,
59E6047A1FE9CB3F00BD90C5 /* RCTMultilineTextInputShadowView.h in Copy Headers */,
59E6047B1FE9CB3F00BD90C5 /* RCTMultilineTextInputView.h in Copy Headers */,
59E6047C1FE9CB3F00BD90C5 /* RCTMultilineTextInputViewManager.h in Copy Headers */,
59E6047D1FE9CB3F00BD90C5 /* RCTUITextView.h in Copy Headers */,
59E6047E1FE9CB3F00BD90C5 /* RCTBackedTextInputDelegate.h in Copy Headers */,
59E6047F1FE9CB3F00BD90C5 /* RCTBackedTextInputDelegateAdapter.h in Copy Headers */,
59E604801FE9CB3F00BD90C5 /* RCTBackedTextInputViewProtocol.h in Copy Headers */,
59E604811FE9CB3F00BD90C5 /* RCTBaseTextInputView.h in Copy Headers */,
59E604821FE9CB3F00BD90C5 /* RCTTextSelection.h in Copy Headers */,
59E604831FE9CB3F00BD90C5 /* RCTSinglelineTextInputShadowView.h in Copy Headers */,
59E604841FE9CB3F00BD90C5 /* RCTSinglelineTextInputView.h in Copy Headers */,
59E604851FE9CB3F00BD90C5 /* RCTSinglelineTextInputViewManager.h in Copy Headers */,
59E604861FE9CB3F00BD90C5 /* RCTUITextField.h in Copy Headers */,
5956B179200FF338008D9D16 /* RCTBaseTextShadowView.h in Copy Headers */,
5956B17A200FF338008D9D16 /* RCTBaseTextViewManager.h in Copy Headers */,
5956B17B200FF338008D9D16 /* RCTRawTextShadowView.h in Copy Headers */,
5956B17C200FF338008D9D16 /* RCTRawTextViewManager.h in Copy Headers */,
5956B17D200FF338008D9D16 /* RCTConvert+Text.h in Copy Headers */,
5956B17E200FF338008D9D16 /* RCTTextAttributes.h in Copy Headers */,
5956B17F200FF338008D9D16 /* NSTextStorage+FontScaling.h in Copy Headers */,
5956B180200FF338008D9D16 /* RCTTextShadowView.h in Copy Headers */,
5956B181200FF338008D9D16 /* RCTTextView.h in Copy Headers */,
5956B182200FF338008D9D16 /* RCTTextViewManager.h in Copy Headers */,
5956B183200FF338008D9D16 /* RCTMultilineTextInputView.h in Copy Headers */,
5956B184200FF338008D9D16 /* RCTMultilineTextInputViewManager.h in Copy Headers */,
5956B185200FF338008D9D16 /* RCTUITextView.h in Copy Headers */,
5956B186200FF338008D9D16 /* RCTBackedTextInputDelegate.h in Copy Headers */,
5956B187200FF338008D9D16 /* RCTBackedTextInputDelegateAdapter.h in Copy Headers */,
5956B188200FF338008D9D16 /* RCTBackedTextInputViewProtocol.h in Copy Headers */,
5956B189200FF338008D9D16 /* RCTBaseTextInputShadowView.h in Copy Headers */,
5956B18A200FF338008D9D16 /* RCTBaseTextInputView.h in Copy Headers */,
5956B18B200FF338008D9D16 /* RCTBaseTextInputViewManager.h in Copy Headers */,
5956B18C200FF338008D9D16 /* RCTTextSelection.h in Copy Headers */,
5956B18D200FF338008D9D16 /* RCTSinglelineTextInputView.h in Copy Headers */,
5956B18E200FF338008D9D16 /* RCTSinglelineTextInputViewManager.h in Copy Headers */,
5956B18F200FF338008D9D16 /* RCTUITextField.h in Copy Headers */,
5956B190200FF338008D9D16 /* RCTVirtualTextShadowView.h in Copy Headers */,
5956B191200FF338008D9D16 /* RCTVirtualTextViewManager.h in Copy Headers */,
);
name = "Copy Headers";
runOnlyForDeploymentPostprocessing = 0;
@ -161,65 +181,70 @@
/* Begin PBXFileReference section */
2D2A287B1D9B048500D4039D /* libRCTText-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRCTText-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
58B5119B1A9E6C1200147676 /* libRCTText.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTText.a; sourceTree = BUILT_PRODUCTS_DIR; };
5989E14B20018A2200EA444A /* RCTBaseTextInputViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBaseTextInputViewManager.h; sourceTree = "<group>"; };
59E6042C1FE9CAF100BD90C5 /* RCTRawTextShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRawTextShadowView.h; sourceTree = "<group>"; };
59E6042E1FE9CAF100BD90C5 /* RCTTextShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextShadowView.h; sourceTree = "<group>"; };
59E6042F1FE9CAF100BD90C5 /* RCTTextShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextShadowView.m; sourceTree = "<group>"; };
59E604301FE9CAF100BD90C5 /* RCTTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextView.h; sourceTree = "<group>"; };
59E604311FE9CAF100BD90C5 /* RCTTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextView.m; sourceTree = "<group>"; };
59E604321FE9CAF100BD90C5 /* RCTTextViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextViewManager.h; sourceTree = "<group>"; };
59E604331FE9CAF100BD90C5 /* RCTTextViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextViewManager.m; sourceTree = "<group>"; };
59E604341FE9CAF100BD90C5 /* RCTRawTextShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRawTextShadowView.m; sourceTree = "<group>"; };
59E604351FE9CAF100BD90C5 /* RCTRawTextViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRawTextViewManager.h; sourceTree = "<group>"; };
59E604361FE9CAF100BD90C5 /* RCTRawTextViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRawTextViewManager.m; sourceTree = "<group>"; };
59E604391FE9CAF100BD90C5 /* RCTMultilineTextInputShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMultilineTextInputShadowView.h; sourceTree = "<group>"; };
59E6043A1FE9CAF100BD90C5 /* RCTMultilineTextInputShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMultilineTextInputShadowView.m; sourceTree = "<group>"; };
59E6043B1FE9CAF100BD90C5 /* RCTMultilineTextInputView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMultilineTextInputView.h; sourceTree = "<group>"; };
59E6043C1FE9CAF100BD90C5 /* RCTMultilineTextInputView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMultilineTextInputView.m; sourceTree = "<group>"; };
59E6043D1FE9CAF100BD90C5 /* RCTMultilineTextInputViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMultilineTextInputViewManager.h; sourceTree = "<group>"; };
59E6043E1FE9CAF100BD90C5 /* RCTMultilineTextInputViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMultilineTextInputViewManager.m; sourceTree = "<group>"; };
59E6043F1FE9CAF100BD90C5 /* RCTUITextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTUITextView.h; sourceTree = "<group>"; };
59E604401FE9CAF100BD90C5 /* RCTUITextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUITextView.m; sourceTree = "<group>"; };
59E604411FE9CAF100BD90C5 /* RCTBackedTextInputDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBackedTextInputDelegate.h; sourceTree = "<group>"; };
59E604421FE9CAF100BD90C5 /* RCTBackedTextInputDelegateAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBackedTextInputDelegateAdapter.h; sourceTree = "<group>"; };
59E604431FE9CAF100BD90C5 /* RCTBackedTextInputDelegateAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBackedTextInputDelegateAdapter.m; sourceTree = "<group>"; };
59E604441FE9CAF100BD90C5 /* RCTBackedTextInputViewProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBackedTextInputViewProtocol.h; sourceTree = "<group>"; };
59E604451FE9CAF100BD90C5 /* RCTBaseTextInputView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBaseTextInputView.h; sourceTree = "<group>"; };
59E604461FE9CAF100BD90C5 /* RCTBaseTextInputView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBaseTextInputView.m; sourceTree = "<group>"; };
59E604471FE9CAF100BD90C5 /* RCTTextSelection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextSelection.h; sourceTree = "<group>"; };
59E604481FE9CAF100BD90C5 /* RCTTextSelection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextSelection.m; sourceTree = "<group>"; };
59E6044A1FE9CAF100BD90C5 /* RCTSinglelineTextInputShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSinglelineTextInputShadowView.h; sourceTree = "<group>"; };
59E6044B1FE9CAF100BD90C5 /* RCTSinglelineTextInputShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSinglelineTextInputShadowView.m; sourceTree = "<group>"; };
59E6044C1FE9CAF100BD90C5 /* RCTSinglelineTextInputView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSinglelineTextInputView.h; sourceTree = "<group>"; };
59E6044D1FE9CAF100BD90C5 /* RCTSinglelineTextInputView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSinglelineTextInputView.m; sourceTree = "<group>"; };
59E6044E1FE9CAF100BD90C5 /* RCTSinglelineTextInputViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSinglelineTextInputViewManager.h; sourceTree = "<group>"; };
59E6044F1FE9CAF100BD90C5 /* RCTSinglelineTextInputViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSinglelineTextInputViewManager.m; sourceTree = "<group>"; };
59E604501FE9CAF100BD90C5 /* RCTUITextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTUITextField.h; sourceTree = "<group>"; };
59E604511FE9CAF100BD90C5 /* RCTUITextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUITextField.m; sourceTree = "<group>"; };
657980E72008B2EE00E908AF /* RCTBaseTextInputViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBaseTextInputViewManager.m; sourceTree = "<group>"; };
A85C82981F742AA20036C019 /* RCTFontAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTFontAttributes.h; sourceTree = "<group>"; };
A85C82991F742AA20036C019 /* RCTFontAttributes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTFontAttributes.m; sourceTree = "<group>"; };
A85C82BA1F742D8F0036C019 /* RCTFontAttributesDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTFontAttributesDelegate.h; sourceTree = "<group>"; };
AF3225F71DE5574F00D3E7E7 /* RCTConvert+Text.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTConvert+Text.h"; sourceTree = "<group>"; };
AF3225F81DE5574F00D3E7E7 /* RCTConvert+Text.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+Text.m"; sourceTree = "<group>"; };
5956B0F9200FEBA9008D9D16 /* RCTConvert+Text.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTConvert+Text.h"; sourceTree = "<group>"; };
5956B0FB200FEBA9008D9D16 /* RCTRawTextViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRawTextViewManager.h; sourceTree = "<group>"; };
5956B0FC200FEBA9008D9D16 /* RCTRawTextShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRawTextShadowView.h; sourceTree = "<group>"; };
5956B0FD200FEBA9008D9D16 /* RCTRawTextShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRawTextShadowView.m; sourceTree = "<group>"; };
5956B0FE200FEBA9008D9D16 /* RCTRawTextViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRawTextViewManager.m; sourceTree = "<group>"; };
5956B101200FEBA9008D9D16 /* RCTSinglelineTextInputView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSinglelineTextInputView.m; sourceTree = "<group>"; };
5956B102200FEBA9008D9D16 /* RCTSinglelineTextInputViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSinglelineTextInputViewManager.h; sourceTree = "<group>"; };
5956B103200FEBA9008D9D16 /* RCTUITextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUITextField.m; sourceTree = "<group>"; };
5956B104200FEBA9008D9D16 /* RCTSinglelineTextInputView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSinglelineTextInputView.h; sourceTree = "<group>"; };
5956B105200FEBA9008D9D16 /* RCTUITextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTUITextField.h; sourceTree = "<group>"; };
5956B106200FEBA9008D9D16 /* RCTSinglelineTextInputViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSinglelineTextInputViewManager.m; sourceTree = "<group>"; };
5956B107200FEBA9008D9D16 /* RCTBackedTextInputDelegateAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBackedTextInputDelegateAdapter.h; sourceTree = "<group>"; };
5956B108200FEBA9008D9D16 /* RCTTextSelection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextSelection.h; sourceTree = "<group>"; };
5956B109200FEBA9008D9D16 /* RCTBaseTextInputShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBaseTextInputShadowView.h; sourceTree = "<group>"; };
5956B10A200FEBA9008D9D16 /* RCTBaseTextInputView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBaseTextInputView.m; sourceTree = "<group>"; };
5956B10B200FEBA9008D9D16 /* RCTBaseTextInputViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBaseTextInputViewManager.h; sourceTree = "<group>"; };
5956B10C200FEBA9008D9D16 /* RCTBackedTextInputDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBackedTextInputDelegate.h; sourceTree = "<group>"; };
5956B10D200FEBA9008D9D16 /* RCTBackedTextInputDelegateAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBackedTextInputDelegateAdapter.m; sourceTree = "<group>"; };
5956B10E200FEBA9008D9D16 /* RCTBaseTextInputView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBaseTextInputView.h; sourceTree = "<group>"; };
5956B10F200FEBA9008D9D16 /* RCTBaseTextInputShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBaseTextInputShadowView.m; sourceTree = "<group>"; };
5956B110200FEBA9008D9D16 /* RCTTextSelection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextSelection.m; sourceTree = "<group>"; };
5956B111200FEBA9008D9D16 /* RCTBaseTextInputViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBaseTextInputViewManager.m; sourceTree = "<group>"; };
5956B112200FEBA9008D9D16 /* RCTBackedTextInputViewProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBackedTextInputViewProtocol.h; sourceTree = "<group>"; };
5956B114200FEBA9008D9D16 /* RCTMultilineTextInputViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMultilineTextInputViewManager.h; sourceTree = "<group>"; };
5956B115200FEBA9008D9D16 /* RCTMultilineTextInputView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMultilineTextInputView.m; sourceTree = "<group>"; };
5956B116200FEBA9008D9D16 /* RCTUITextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTUITextView.h; sourceTree = "<group>"; };
5956B117200FEBA9008D9D16 /* RCTMultilineTextInputView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMultilineTextInputView.h; sourceTree = "<group>"; };
5956B118200FEBA9008D9D16 /* RCTMultilineTextInputViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMultilineTextInputViewManager.m; sourceTree = "<group>"; };
5956B119200FEBA9008D9D16 /* RCTUITextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUITextView.m; sourceTree = "<group>"; };
5956B11A200FEBA9008D9D16 /* RCTTextAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextAttributes.h; sourceTree = "<group>"; };
5956B11C200FEBA9008D9D16 /* RCTBaseTextViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBaseTextViewManager.h; sourceTree = "<group>"; };
5956B11D200FEBA9008D9D16 /* RCTBaseTextShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBaseTextShadowView.h; sourceTree = "<group>"; };
5956B11E200FEBA9008D9D16 /* RCTBaseTextShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBaseTextShadowView.m; sourceTree = "<group>"; };
5956B11F200FEBA9008D9D16 /* RCTBaseTextViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBaseTextViewManager.m; sourceTree = "<group>"; };
5956B120200FEBA9008D9D16 /* RCTTextAttributes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextAttributes.m; sourceTree = "<group>"; };
5956B122200FEBAA008D9D16 /* RCTTextShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextShadowView.m; sourceTree = "<group>"; };
5956B123200FEBAA008D9D16 /* RCTTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextView.h; sourceTree = "<group>"; };
5956B124200FEBAA008D9D16 /* RCTTextViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextViewManager.h; sourceTree = "<group>"; };
5956B125200FEBAA008D9D16 /* NSTextStorage+FontScaling.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSTextStorage+FontScaling.m"; sourceTree = "<group>"; };
5956B126200FEBAA008D9D16 /* RCTTextShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextShadowView.h; sourceTree = "<group>"; };
5956B127200FEBAA008D9D16 /* RCTTextViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextViewManager.m; sourceTree = "<group>"; };
5956B128200FEBAA008D9D16 /* RCTTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextView.m; sourceTree = "<group>"; };
5956B129200FEBAA008D9D16 /* NSTextStorage+FontScaling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSTextStorage+FontScaling.h"; sourceTree = "<group>"; };
5956B12B200FEBAA008D9D16 /* RCTVirtualTextViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTVirtualTextViewManager.m; sourceTree = "<group>"; };
5956B12C200FEBAA008D9D16 /* RCTVirtualTextShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTVirtualTextShadowView.h; sourceTree = "<group>"; };
5956B12D200FEBAA008D9D16 /* RCTVirtualTextViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTVirtualTextViewManager.h; sourceTree = "<group>"; };
5956B12E200FEBAA008D9D16 /* RCTVirtualTextShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTVirtualTextShadowView.m; sourceTree = "<group>"; };
5956B12F200FEBAA008D9D16 /* RCTConvert+Text.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+Text.m"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXGroup section */
58B511921A9E6C1200147676 = {
isa = PBXGroup;
children = (
5956B11B200FEBA9008D9D16 /* BaseText */,
58B5119C1A9E6C1200147676 /* Products */,
AF3225F71DE5574F00D3E7E7 /* RCTConvert+Text.h */,
AF3225F81DE5574F00D3E7E7 /* RCTConvert+Text.m */,
A85C82981F742AA20036C019 /* RCTFontAttributes.h */,
A85C82991F742AA20036C019 /* RCTFontAttributes.m */,
A85C82BA1F742D8F0036C019 /* RCTFontAttributesDelegate.h */,
59E6042C1FE9CAF100BD90C5 /* RCTRawTextShadowView.h */,
59E604341FE9CAF100BD90C5 /* RCTRawTextShadowView.m */,
59E604351FE9CAF100BD90C5 /* RCTRawTextViewManager.h */,
59E604361FE9CAF100BD90C5 /* RCTRawTextViewManager.m */,
59E6042D1FE9CAF100BD90C5 /* Text */,
59E604371FE9CAF100BD90C5 /* TextInput */,
5956B0FA200FEBA9008D9D16 /* RawText */,
5956B0F9200FEBA9008D9D16 /* RCTConvert+Text.h */,
5956B12F200FEBAA008D9D16 /* RCTConvert+Text.m */,
5956B11A200FEBA9008D9D16 /* RCTTextAttributes.h */,
5956B120200FEBA9008D9D16 /* RCTTextAttributes.m */,
5956B121200FEBAA008D9D16 /* Text */,
5956B0FF200FEBA9008D9D16 /* TextInput */,
5956B12A200FEBAA008D9D16 /* VirtualText */,
);
indentWidth = 2;
sourceTree = "<group>";
@ -235,66 +260,99 @@
name = Products;
sourceTree = "<group>";
};
59E6042D1FE9CAF100BD90C5 /* Text */ = {
5956B0FA200FEBA9008D9D16 /* RawText */ = {
isa = PBXGroup;
children = (
59E6042E1FE9CAF100BD90C5 /* RCTTextShadowView.h */,
59E6042F1FE9CAF100BD90C5 /* RCTTextShadowView.m */,
59E604301FE9CAF100BD90C5 /* RCTTextView.h */,
59E604311FE9CAF100BD90C5 /* RCTTextView.m */,
59E604321FE9CAF100BD90C5 /* RCTTextViewManager.h */,
59E604331FE9CAF100BD90C5 /* RCTTextViewManager.m */,
5956B0FC200FEBA9008D9D16 /* RCTRawTextShadowView.h */,
5956B0FD200FEBA9008D9D16 /* RCTRawTextShadowView.m */,
5956B0FB200FEBA9008D9D16 /* RCTRawTextViewManager.h */,
5956B0FE200FEBA9008D9D16 /* RCTRawTextViewManager.m */,
);
path = Text;
path = RawText;
sourceTree = "<group>";
};
59E604371FE9CAF100BD90C5 /* TextInput */ = {
5956B0FF200FEBA9008D9D16 /* TextInput */ = {
isa = PBXGroup;
children = (
657980E72008B2EE00E908AF /* RCTBaseTextInputViewManager.m */,
59E604381FE9CAF100BD90C5 /* Multiline */,
59E604411FE9CAF100BD90C5 /* RCTBackedTextInputDelegate.h */,
59E604421FE9CAF100BD90C5 /* RCTBackedTextInputDelegateAdapter.h */,
59E604431FE9CAF100BD90C5 /* RCTBackedTextInputDelegateAdapter.m */,
59E604441FE9CAF100BD90C5 /* RCTBackedTextInputViewProtocol.h */,
59E604451FE9CAF100BD90C5 /* RCTBaseTextInputView.h */,
59E604461FE9CAF100BD90C5 /* RCTBaseTextInputView.m */,
5989E14B20018A2200EA444A /* RCTBaseTextInputViewManager.h */,
59E604471FE9CAF100BD90C5 /* RCTTextSelection.h */,
59E604481FE9CAF100BD90C5 /* RCTTextSelection.m */,
59E604491FE9CAF100BD90C5 /* Singleline */,
5956B113200FEBA9008D9D16 /* Multiline */,
5956B10C200FEBA9008D9D16 /* RCTBackedTextInputDelegate.h */,
5956B107200FEBA9008D9D16 /* RCTBackedTextInputDelegateAdapter.h */,
5956B10D200FEBA9008D9D16 /* RCTBackedTextInputDelegateAdapter.m */,
5956B112200FEBA9008D9D16 /* RCTBackedTextInputViewProtocol.h */,
5956B109200FEBA9008D9D16 /* RCTBaseTextInputShadowView.h */,
5956B10F200FEBA9008D9D16 /* RCTBaseTextInputShadowView.m */,
5956B10E200FEBA9008D9D16 /* RCTBaseTextInputView.h */,
5956B10A200FEBA9008D9D16 /* RCTBaseTextInputView.m */,
5956B10B200FEBA9008D9D16 /* RCTBaseTextInputViewManager.h */,
5956B111200FEBA9008D9D16 /* RCTBaseTextInputViewManager.m */,
5956B108200FEBA9008D9D16 /* RCTTextSelection.h */,
5956B110200FEBA9008D9D16 /* RCTTextSelection.m */,
5956B100200FEBA9008D9D16 /* Singleline */,
);
path = TextInput;
sourceTree = "<group>";
};
59E604381FE9CAF100BD90C5 /* Multiline */ = {
5956B100200FEBA9008D9D16 /* Singleline */ = {
isa = PBXGroup;
children = (
59E604391FE9CAF100BD90C5 /* RCTMultilineTextInputShadowView.h */,
59E6043A1FE9CAF100BD90C5 /* RCTMultilineTextInputShadowView.m */,
59E6043B1FE9CAF100BD90C5 /* RCTMultilineTextInputView.h */,
59E6043C1FE9CAF100BD90C5 /* RCTMultilineTextInputView.m */,
59E6043D1FE9CAF100BD90C5 /* RCTMultilineTextInputViewManager.h */,
59E6043E1FE9CAF100BD90C5 /* RCTMultilineTextInputViewManager.m */,
59E6043F1FE9CAF100BD90C5 /* RCTUITextView.h */,
59E604401FE9CAF100BD90C5 /* RCTUITextView.m */,
5956B104200FEBA9008D9D16 /* RCTSinglelineTextInputView.h */,
5956B101200FEBA9008D9D16 /* RCTSinglelineTextInputView.m */,
5956B102200FEBA9008D9D16 /* RCTSinglelineTextInputViewManager.h */,
5956B106200FEBA9008D9D16 /* RCTSinglelineTextInputViewManager.m */,
5956B105200FEBA9008D9D16 /* RCTUITextField.h */,
5956B103200FEBA9008D9D16 /* RCTUITextField.m */,
);
path = Singleline;
sourceTree = "<group>";
};
5956B113200FEBA9008D9D16 /* Multiline */ = {
isa = PBXGroup;
children = (
5956B117200FEBA9008D9D16 /* RCTMultilineTextInputView.h */,
5956B115200FEBA9008D9D16 /* RCTMultilineTextInputView.m */,
5956B114200FEBA9008D9D16 /* RCTMultilineTextInputViewManager.h */,
5956B118200FEBA9008D9D16 /* RCTMultilineTextInputViewManager.m */,
5956B116200FEBA9008D9D16 /* RCTUITextView.h */,
5956B119200FEBA9008D9D16 /* RCTUITextView.m */,
);
path = Multiline;
sourceTree = "<group>";
};
59E604491FE9CAF100BD90C5 /* Singleline */ = {
5956B11B200FEBA9008D9D16 /* BaseText */ = {
isa = PBXGroup;
children = (
59E6044A1FE9CAF100BD90C5 /* RCTSinglelineTextInputShadowView.h */,
59E6044B1FE9CAF100BD90C5 /* RCTSinglelineTextInputShadowView.m */,
59E6044C1FE9CAF100BD90C5 /* RCTSinglelineTextInputView.h */,
59E6044D1FE9CAF100BD90C5 /* RCTSinglelineTextInputView.m */,
59E6044E1FE9CAF100BD90C5 /* RCTSinglelineTextInputViewManager.h */,
59E6044F1FE9CAF100BD90C5 /* RCTSinglelineTextInputViewManager.m */,
59E604501FE9CAF100BD90C5 /* RCTUITextField.h */,
59E604511FE9CAF100BD90C5 /* RCTUITextField.m */,
5956B11D200FEBA9008D9D16 /* RCTBaseTextShadowView.h */,
5956B11E200FEBA9008D9D16 /* RCTBaseTextShadowView.m */,
5956B11C200FEBA9008D9D16 /* RCTBaseTextViewManager.h */,
5956B11F200FEBA9008D9D16 /* RCTBaseTextViewManager.m */,
);
path = Singleline;
path = BaseText;
sourceTree = "<group>";
};
5956B121200FEBAA008D9D16 /* Text */ = {
isa = PBXGroup;
children = (
5956B129200FEBAA008D9D16 /* NSTextStorage+FontScaling.h */,
5956B125200FEBAA008D9D16 /* NSTextStorage+FontScaling.m */,
5956B126200FEBAA008D9D16 /* RCTTextShadowView.h */,
5956B122200FEBAA008D9D16 /* RCTTextShadowView.m */,
5956B123200FEBAA008D9D16 /* RCTTextView.h */,
5956B128200FEBAA008D9D16 /* RCTTextView.m */,
5956B124200FEBAA008D9D16 /* RCTTextViewManager.h */,
5956B127200FEBAA008D9D16 /* RCTTextViewManager.m */,
);
path = Text;
sourceTree = "<group>";
};
5956B12A200FEBAA008D9D16 /* VirtualText */ = {
isa = PBXGroup;
children = (
5956B12C200FEBAA008D9D16 /* RCTVirtualTextShadowView.h */,
5956B12E200FEBAA008D9D16 /* RCTVirtualTextShadowView.m */,
5956B12D200FEBAA008D9D16 /* RCTVirtualTextViewManager.h */,
5956B12B200FEBAA008D9D16 /* RCTVirtualTextViewManager.m */,
);
path = VirtualText;
sourceTree = "<group>";
};
/* End PBXGroup section */
@ -373,25 +431,29 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
59E6046F1FE9CAF100BD90C5 /* RCTSinglelineTextInputViewManager.m in Sources */,
59E604671FE9CAF100BD90C5 /* RCTBaseTextInputView.m in Sources */,
59E604631FE9CAF100BD90C5 /* RCTUITextView.m in Sources */,
59E604571FE9CAF100BD90C5 /* RCTTextViewManager.m in Sources */,
59E604531FE9CAF100BD90C5 /* RCTTextShadowView.m in Sources */,
59E6045D1FE9CAF100BD90C5 /* RCTMultilineTextInputShadowView.m in Sources */,
59E604591FE9CAF100BD90C5 /* RCTRawTextShadowView.m in Sources */,
59E604551FE9CAF100BD90C5 /* RCTTextView.m in Sources */,
657980E92008B2FA00E908AF /* RCTBaseTextInputViewManager.m in Sources */,
59E604691FE9CAF100BD90C5 /* RCTTextSelection.m in Sources */,
59E6045F1FE9CAF100BD90C5 /* RCTMultilineTextInputView.m in Sources */,
59E604611FE9CAF100BD90C5 /* RCTMultilineTextInputViewManager.m in Sources */,
59E604711FE9CAF100BD90C5 /* RCTUITextField.m in Sources */,
59E6046D1FE9CAF100BD90C5 /* RCTSinglelineTextInputView.m in Sources */,
59E8C5CC1F8833D100204F5E /* RCTFontAttributes.m in Sources */,
59E6045B1FE9CAF100BD90C5 /* RCTRawTextViewManager.m in Sources */,
59E604651FE9CAF100BD90C5 /* RCTBackedTextInputDelegateAdapter.m in Sources */,
AF3225FA1DE5574F00D3E7E7 /* RCTConvert+Text.m in Sources */,
59E6046B1FE9CAF100BD90C5 /* RCTSinglelineTextInputShadowView.m in Sources */,
5956B192200FF35C008D9D16 /* RCTBaseTextShadowView.m in Sources */,
5956B193200FF35C008D9D16 /* RCTBaseTextViewManager.m in Sources */,
5956B194200FF35C008D9D16 /* RCTRawTextShadowView.m in Sources */,
5956B195200FF35C008D9D16 /* RCTRawTextViewManager.m in Sources */,
5956B196200FF35C008D9D16 /* RCTConvert+Text.m in Sources */,
5956B197200FF35C008D9D16 /* RCTTextAttributes.m in Sources */,
5956B198200FF35C008D9D16 /* NSTextStorage+FontScaling.m in Sources */,
5956B199200FF35C008D9D16 /* RCTTextShadowView.m in Sources */,
5956B19A200FF35C008D9D16 /* RCTTextView.m in Sources */,
5956B19B200FF35C008D9D16 /* RCTTextViewManager.m in Sources */,
5956B19C200FF35C008D9D16 /* RCTMultilineTextInputView.m in Sources */,
5956B19D200FF35C008D9D16 /* RCTMultilineTextInputViewManager.m in Sources */,
5956B19E200FF35C008D9D16 /* RCTUITextView.m in Sources */,
5956B19F200FF35C008D9D16 /* RCTBackedTextInputDelegateAdapter.m in Sources */,
5956B1A0200FF35C008D9D16 /* RCTBaseTextInputShadowView.m in Sources */,
5956B1A1200FF35C008D9D16 /* RCTBaseTextInputView.m in Sources */,
5956B1A2200FF35C008D9D16 /* RCTBaseTextInputViewManager.m in Sources */,
5956B1A3200FF35C008D9D16 /* RCTTextSelection.m in Sources */,
5956B1A4200FF35C008D9D16 /* RCTSinglelineTextInputView.m in Sources */,
5956B1A5200FF35C008D9D16 /* RCTSinglelineTextInputViewManager.m in Sources */,
5956B1A6200FF35C008D9D16 /* RCTUITextField.m in Sources */,
5956B1A7200FF35C008D9D16 /* RCTVirtualTextShadowView.m in Sources */,
5956B1A8200FF35C008D9D16 /* RCTVirtualTextViewManager.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -399,25 +461,29 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
59E6046E1FE9CAF100BD90C5 /* RCTSinglelineTextInputViewManager.m in Sources */,
59E604661FE9CAF100BD90C5 /* RCTBaseTextInputView.m in Sources */,
59E604621FE9CAF100BD90C5 /* RCTUITextView.m in Sources */,
59E604561FE9CAF100BD90C5 /* RCTTextViewManager.m in Sources */,
59E604521FE9CAF100BD90C5 /* RCTTextShadowView.m in Sources */,
59E6045C1FE9CAF100BD90C5 /* RCTMultilineTextInputShadowView.m in Sources */,
59E604581FE9CAF100BD90C5 /* RCTRawTextShadowView.m in Sources */,
59E604541FE9CAF100BD90C5 /* RCTTextView.m in Sources */,
657980E82008B2EE00E908AF /* RCTBaseTextInputViewManager.m in Sources */,
59E604681FE9CAF100BD90C5 /* RCTTextSelection.m in Sources */,
59E6045E1FE9CAF100BD90C5 /* RCTMultilineTextInputView.m in Sources */,
59E604601FE9CAF100BD90C5 /* RCTMultilineTextInputViewManager.m in Sources */,
59E604701FE9CAF100BD90C5 /* RCTUITextField.m in Sources */,
59E6046C1FE9CAF100BD90C5 /* RCTSinglelineTextInputView.m in Sources */,
A85C829A1F742AA20036C019 /* RCTFontAttributes.m in Sources */,
59E6045A1FE9CAF100BD90C5 /* RCTRawTextViewManager.m in Sources */,
59E604641FE9CAF100BD90C5 /* RCTBackedTextInputDelegateAdapter.m in Sources */,
AF3225F91DE5574F00D3E7E7 /* RCTConvert+Text.m in Sources */,
59E6046A1FE9CAF100BD90C5 /* RCTSinglelineTextInputShadowView.m in Sources */,
5956B13D200FEBAA008D9D16 /* RCTBaseTextShadowView.m in Sources */,
5956B140200FEBAA008D9D16 /* RCTTextShadowView.m in Sources */,
5956B131200FEBAA008D9D16 /* RCTRawTextViewManager.m in Sources */,
5956B137200FEBAA008D9D16 /* RCTBaseTextInputShadowView.m in Sources */,
5956B146200FEBAA008D9D16 /* RCTConvert+Text.m in Sources */,
5956B13F200FEBAA008D9D16 /* RCTTextAttributes.m in Sources */,
5956B143200FEBAA008D9D16 /* RCTTextView.m in Sources */,
5956B13C200FEBAA008D9D16 /* RCTUITextView.m in Sources */,
5956B136200FEBAA008D9D16 /* RCTBackedTextInputDelegateAdapter.m in Sources */,
5956B13E200FEBAA008D9D16 /* RCTBaseTextViewManager.m in Sources */,
5956B145200FEBAA008D9D16 /* RCTVirtualTextShadowView.m in Sources */,
5956B142200FEBAA008D9D16 /* RCTTextViewManager.m in Sources */,
5956B135200FEBAA008D9D16 /* RCTBaseTextInputView.m in Sources */,
5956B144200FEBAA008D9D16 /* RCTVirtualTextViewManager.m in Sources */,
5956B13B200FEBAA008D9D16 /* RCTMultilineTextInputViewManager.m in Sources */,
5956B134200FEBAA008D9D16 /* RCTSinglelineTextInputViewManager.m in Sources */,
5956B139200FEBAA008D9D16 /* RCTBaseTextInputViewManager.m in Sources */,
5956B138200FEBAA008D9D16 /* RCTTextSelection.m in Sources */,
5956B130200FEBAA008D9D16 /* RCTRawTextShadowView.m in Sources */,
5956B141200FEBAA008D9D16 /* NSTextStorage+FontScaling.m in Sources */,
5956B132200FEBAA008D9D16 /* RCTSinglelineTextInputView.m in Sources */,
5956B13A200FEBAA008D9D16 /* RCTMultilineTextInputView.m in Sources */,
5956B133200FEBAA008D9D16 /* RCTUITextField.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -0,0 +1,85 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
#import <React/RCTTextDecorationLineType.h>
NS_ASSUME_NONNULL_BEGIN
extern NSString *const RCTTextAttributesIsHighlightedAttributeName;
extern NSString *const RCTTextAttributesTagAttributeName;
/**
* Represents knowledge about all supported *text* attributes
* assigned to some text component such as <Text>, <VirtualText>,
* and <TextInput>.
*/
@interface RCTTextAttributes : NSObject <NSCopying>
// Color
@property (nonatomic, strong, nullable) UIColor *foregroundColor;
@property (nonatomic, strong, nullable) UIColor *backgroundColor;
@property (nonatomic, assign) CGFloat opacity;
// Font
@property (nonatomic, copy, nullable) NSString *fontFamily;
@property (nonatomic, assign) CGFloat fontSize;
@property (nonatomic, assign) CGFloat fontSizeMultiplier;
@property (nonatomic, copy, nullable) NSString *fontWeight;
@property (nonatomic, copy, nullable) NSString *fontStyle;
@property (nonatomic, copy, nullable) NSArray<NSString *> *fontVariant;
@property (nonatomic, assign) BOOL allowFontScaling;
@property (nonatomic, assign) CGFloat letterSpacing;
// Paragraph Styles
@property (nonatomic, assign) CGFloat lineHeight;
@property (nonatomic, assign) NSTextAlignment alignment;
@property (nonatomic, assign) NSWritingDirection baseWritingDirection;
// Decoration
@property (nonatomic, strong, nullable) UIColor *textDecorationColor;
@property (nonatomic, assign) NSUnderlineStyle textDecorationStyle;
@property (nonatomic, assign) RCTTextDecorationLineType textDecorationLine;
// Shadow
@property (nonatomic, assign) CGSize textShadowOffset;
@property (nonatomic, assign) CGFloat textShadowRadius;
@property (nonatomic, strong, nullable) UIColor *textShadowColor;
// Special
@property (nonatomic, assign) BOOL isHighlighted;
@property (nonatomic, strong, nullable) NSNumber *tag;
@property (nonatomic, assign) UIUserInterfaceLayoutDirection layoutDirection;
#pragma mark - Inheritance
- (void)applyTextAttributes:(RCTTextAttributes *)textAttributes;
#pragma mark - Adapters
/**
* Text attributes in NSAttributedString terms.
*/
- (NSDictionary<NSAttributedStringKey, id> *)effectiveTextAttributes;
/**
* Constructed font.
*/
- (UIFont *)effectiveFont;
/**
* Font size multiplier reflects `allowFontScaling` and `fontSizeMultiplier`.
*/
- (CGFloat)effectiveFontSizeMultiplier;
/**
* Foreground and background colors with opacity and right defaults.
*/
- (UIColor *)effectiveForegroundColor;
- (UIColor *)effectiveBackgroundColor;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,270 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTTextAttributes.h"
#import <React/RCTAssert.h>
#import <React/RCTFont.h>
#import <React/RCTLog.h>
NSString *const RCTTextAttributesIsHighlightedAttributeName = @"RCTTextAttributesIsHighlightedAttributeName";
NSString *const RCTTextAttributesTagAttributeName = @"RCTTextAttributesTagAttributeName";
@implementation RCTTextAttributes
- (instancetype)init
{
if (self = [super init]) {
_fontSize = NAN;
_letterSpacing = NAN;
_textDecorationStyle = NSUnderlineStyleSingle;
_fontSizeMultiplier = NAN;
_alignment = NSTextAlignmentNatural;
_baseWritingDirection = NSWritingDirectionNatural;
_textShadowRadius = NAN;
_opacity = NAN;
}
return self;
}
- (void)applyTextAttributes:(RCTTextAttributes *)textAttributes
{
// Note: All lines marked with `*` does not use explicit/correct rules to compare old and new values becuase
// their types do not have special designated value representing undefined/unspecified/inherit meaning.
// We will address this in the future.
// Color
_foregroundColor = textAttributes->_foregroundColor ?: _foregroundColor;
_backgroundColor = textAttributes->_backgroundColor ?: _backgroundColor;
_opacity = !isnan(textAttributes->_opacity) ? (isnan(_opacity) ? 1.0 : _opacity) * textAttributes->_opacity : _opacity;
// Font
_fontFamily = textAttributes->_fontFamily ?: _fontFamily;
_fontSize = !isnan(textAttributes->_fontSize) ? textAttributes->_fontSize : _fontSize;
_fontSizeMultiplier = !isnan(textAttributes->_fontSizeMultiplier) ? textAttributes->_fontSizeMultiplier : _fontSizeMultiplier;
_fontWeight = textAttributes->_fontWeight ?: _fontWeight;
_fontStyle = textAttributes->_fontStyle ?: _fontStyle;
_fontVariant = textAttributes->_fontVariant ?: _fontVariant;
_allowFontScaling = textAttributes->_allowFontScaling || _allowFontScaling; // *
_letterSpacing = !isnan(textAttributes->_letterSpacing) ? textAttributes->_letterSpacing : _letterSpacing;
// Paragraph Styles
_lineHeight = !isnan(textAttributes->_lineHeight) ? textAttributes->_lineHeight : _lineHeight;
_alignment = textAttributes->_alignment != NSTextAlignmentNatural ? textAttributes->_alignment : _alignment; // *
_baseWritingDirection = textAttributes->_baseWritingDirection != NSWritingDirectionNatural ? textAttributes->_baseWritingDirection : _baseWritingDirection; // *
// Decoration
_textDecorationColor = textAttributes->_textDecorationColor ?: _textDecorationColor;
_textDecorationStyle = textAttributes->_textDecorationStyle != NSUnderlineStyleSingle ? textAttributes->_textDecorationStyle : _textDecorationStyle; // *
_textDecorationLine = textAttributes->_textDecorationLine != RCTTextDecorationLineTypeNone ? textAttributes->_textDecorationLine : _textDecorationLine; // *
// Shadow
_textShadowOffset = !CGSizeEqualToSize(textAttributes->_textShadowOffset, CGSizeZero) ? textAttributes->_textShadowOffset : _textShadowOffset; // *
_textShadowRadius = !isnan(textAttributes->_textShadowRadius) ? textAttributes->_textShadowRadius : _textShadowRadius;
_textShadowColor = textAttributes->_textShadowColor ?: _textShadowColor;
// Special
_isHighlighted = textAttributes->_isHighlighted || _isHighlighted; // *
_tag = textAttributes->_tag ?: _tag;
_layoutDirection = textAttributes->_layoutDirection != UIUserInterfaceLayoutDirectionLeftToRight ? textAttributes->_layoutDirection : _layoutDirection;
}
- (NSDictionary<NSAttributedStringKey, id> *)effectiveTextAttributes
{
NSMutableDictionary<NSAttributedStringKey, id> *attributes =
[NSMutableDictionary dictionaryWithCapacity:10];
// Font
UIFont *font = self.effectiveFont;
if (font) {
attributes[NSFontAttributeName] = font;
}
// Colors
UIColor *effectiveForegroundColor = self.effectiveForegroundColor;
if (_foregroundColor || !isnan(_opacity)) {
attributes[NSForegroundColorAttributeName] = effectiveForegroundColor;
}
if (_backgroundColor || !isnan(_opacity)) {
attributes[NSBackgroundColorAttributeName] = self.effectiveBackgroundColor;
}
// Kerning
if (!isnan(_letterSpacing)) {
attributes[NSKernAttributeName] = @(_letterSpacing);
}
// Paragraph Style
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
BOOL isParagraphStyleUsed = NO;
if (_alignment != NSTextAlignmentNatural) {
NSTextAlignment alignment = _alignment;
if (_layoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) {
if (alignment == NSTextAlignmentRight) {
alignment = NSTextAlignmentLeft;
} else if (alignment == NSTextAlignmentLeft) {
alignment = NSTextAlignmentRight;
}
}
paragraphStyle.alignment = alignment;
isParagraphStyleUsed = YES;
}
if (_baseWritingDirection != NSWritingDirectionNatural) {
paragraphStyle.baseWritingDirection = _baseWritingDirection;
isParagraphStyleUsed = YES;
}
if (!isnan(_lineHeight)) {
CGFloat lineHeight = _lineHeight * self.effectiveFontSizeMultiplier;
paragraphStyle.minimumLineHeight = lineHeight;
paragraphStyle.maximumLineHeight = lineHeight;
isParagraphStyleUsed = YES;
}
if (isParagraphStyleUsed) {
attributes[NSParagraphStyleAttributeName] = paragraphStyle;
}
// Decoration
BOOL isTextDecorationEnabled = NO;
if (_textDecorationLine == RCTTextDecorationLineTypeUnderline ||
_textDecorationLine == RCTTextDecorationLineTypeUnderlineStrikethrough) {
isTextDecorationEnabled = YES;
attributes[NSUnderlineStyleAttributeName] = @(_textDecorationStyle);
}
if (_textDecorationLine == RCTTextDecorationLineTypeStrikethrough ||
_textDecorationLine == RCTTextDecorationLineTypeUnderlineStrikethrough){
isTextDecorationEnabled = YES;
attributes[NSStrikethroughStyleAttributeName] = @(_textDecorationStyle);
}
if (_textDecorationColor || isTextDecorationEnabled) {
attributes[NSStrikethroughColorAttributeName] = _textDecorationColor ?: effectiveForegroundColor;
attributes[NSUnderlineColorAttributeName] = _textDecorationColor ?: effectiveForegroundColor;
}
// Shadow
if (!CGSizeEqualToSize(_textShadowOffset, CGSizeZero)) {
NSShadow *shadow = [NSShadow new];
shadow.shadowOffset = _textShadowOffset;
shadow.shadowBlurRadius = _textShadowRadius;
shadow.shadowColor = _textShadowColor;
attributes[NSShadowAttributeName] = shadow;
}
// Special
if (_isHighlighted) {
attributes[RCTTextAttributesIsHighlightedAttributeName] = @YES;
}
if (_tag) {
attributes[RCTTextAttributesTagAttributeName] = _tag;
}
return [attributes copy];
}
- (UIFont *)effectiveFont
{
// FIXME: RCTFont has thread-safety issues and must be rewritten.
return [RCTFont updateFont:nil
withFamily:_fontFamily
size:@(isnan(_fontSize) ? 0 : _fontSize)
weight:_fontWeight
style:_fontStyle
variant:_fontVariant
scaleMultiplier:self.effectiveFontSizeMultiplier];
}
- (CGFloat)effectiveFontSizeMultiplier
{
return _allowFontScaling && !isnan(_fontSizeMultiplier) ? _fontSizeMultiplier : 1.0;
}
- (UIColor *)effectiveForegroundColor
{
UIColor *effectiveForegroundColor = _foregroundColor ?: [UIColor blackColor];
if (!isnan(_opacity)) {
effectiveForegroundColor = [effectiveForegroundColor colorWithAlphaComponent:CGColorGetAlpha(effectiveForegroundColor.CGColor) * _opacity];
}
return effectiveForegroundColor;
}
- (UIColor *)effectiveBackgroundColor
{
UIColor *effectiveBackgroundColor = _backgroundColor;// ?: [[UIColor whiteColor] colorWithAlphaComponent:0];
if (effectiveBackgroundColor && !isnan(_opacity)) {
effectiveBackgroundColor = [effectiveBackgroundColor colorWithAlphaComponent:CGColorGetAlpha(effectiveBackgroundColor.CGColor) * _opacity];
}
return effectiveBackgroundColor ?: [UIColor clearColor];
}
- (RCTTextAttributes *)copyWithZone:(NSZone *)zone
{
RCTTextAttributes *textAttributes = [RCTTextAttributes new];
[textAttributes applyTextAttributes:self];
return textAttributes;
}
#pragma mark - NSObject
- (BOOL)isEqual:(RCTTextAttributes *)textAttributes
{
if (self == textAttributes) {
return YES;
}
#define RCTTextAttributesCompareFloats(a) ((a == textAttributes->a) || (isnan(a) && isnan(textAttributes->a)))
#define RCTTextAttributesCompareSize(a) CGSizeEqualToSize(a, textAttributes->a)
#define RCTTextAttributesCompareObjects(a) ((a == textAttributes->a) || [a isEqual:textAttributes->a])
#define RCTTextAttributesCompareStrings(a) ((a == textAttributes->a) || [a isEqualToString:textAttributes->a])
#define RCTTextAttributesCompareOthers(a) (a == textAttributes->a)
return
RCTTextAttributesCompareObjects(_foregroundColor) &&
RCTTextAttributesCompareObjects(_backgroundColor) &&
RCTTextAttributesCompareFloats(_opacity) &&
// Font
RCTTextAttributesCompareObjects(_fontFamily) &&
RCTTextAttributesCompareFloats(_fontSize) &&
RCTTextAttributesCompareFloats(_fontSizeMultiplier) &&
RCTTextAttributesCompareStrings(_fontWeight) &&
RCTTextAttributesCompareObjects(_fontStyle) &&
RCTTextAttributesCompareObjects(_fontVariant) &&
RCTTextAttributesCompareOthers(_allowFontScaling) &&
RCTTextAttributesCompareFloats(_letterSpacing) &&
// Paragraph Styles
RCTTextAttributesCompareFloats(_lineHeight) &&
RCTTextAttributesCompareFloats(_alignment) &&
RCTTextAttributesCompareOthers(_baseWritingDirection) &&
// Decoration
RCTTextAttributesCompareObjects(_textDecorationColor) &&
RCTTextAttributesCompareOthers(_textDecorationStyle) &&
RCTTextAttributesCompareOthers(_textDecorationLine) &&
// Shadow
RCTTextAttributesCompareSize(_textShadowOffset) &&
RCTTextAttributesCompareFloats(_textShadowRadius) &&
RCTTextAttributesCompareObjects(_textShadowColor) &&
// Special
RCTTextAttributesCompareOthers(_isHighlighted) &&
RCTTextAttributesCompareObjects(_tag) &&
RCTTextAttributesCompareOthers(_layoutDirection);
}
@end

View File

@ -9,8 +9,12 @@
#import <React/RCTShadowView.h>
NS_ASSUME_NONNULL_BEGIN
@interface RCTRawTextShadowView : RCTShadowView
@property (nonatomic, copy) NSString *text;
@property (nonatomic, copy, nullable) NSString *text;
@end
NS_ASSUME_NONNULL_END

View File

@ -9,39 +9,23 @@
#import "RCTRawTextShadowView.h"
#import <React/RCTUIManager.h>
#import <React/RCTShadowView+Layout.h>
@implementation RCTRawTextShadowView
- (instancetype)init
{
if ((self = [super init])) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(contentSizeMultiplierDidChange:)
name:RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotification
object:nil];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)contentSizeMultiplierDidChange:(NSNotification *)note
{
[self dirtyText];
}
- (void)setText:(NSString *)text
{
if (_text != text && ![_text isEqualToString:text]) {
_text = [text copy];
[self dirtyText];
[self dirtyLayout];
}
}
- (void)dirtyLayout
{
[self.superview dirtyLayout];
}
- (NSString *)description
{
NSString *superDescription = super.description;

View File

@ -9,6 +9,10 @@
#import <React/RCTViewManager.h>
NS_ASSUME_NONNULL_BEGIN
@interface RCTRawTextViewManager : RCTViewManager
@end
NS_ASSUME_NONNULL_END

View File

@ -15,6 +15,11 @@
RCT_EXPORT_MODULE(RCTRawText)
- (UIView *)view
{
return [UIView new];
}
- (RCTShadowView *)shadowView
{
return [RCTRawTextShadowView new];

View File

@ -0,0 +1,22 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
@interface NSTextStorage (FontScaling)
- (void)scaleFontSizeToFitSize:(CGSize)size
minimumFontSize:(CGFloat)minimumFontSize
maximumFontSize:(CGFloat)maximumFontSize;
- (void)scaleFontSizeWithRatio:(CGFloat)ratio
minimumFontSize:(CGFloat)minimumFontSize
maximumFontSize:(CGFloat)maximumFontSize;
@end

View File

@ -0,0 +1,139 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "NSTextStorage+FontScaling.h"
typedef NS_OPTIONS(NSInteger, RCTTextSizeComparisonOptions) {
RCTTextSizeComparisonSmaller = 1 << 0,
RCTTextSizeComparisonLarger = 1 << 1,
RCTTextSizeComparisonWithinRange = 1 << 2,
};
@implementation NSTextStorage (FontScaling)
- (void)scaleFontSizeToFitSize:(CGSize)size
minimumFontSize:(CGFloat)minimumFontSize
maximumFontSize:(CGFloat)maximumFontSize
{
CGFloat bottomRatio = 1.0/128.0;
CGFloat topRatio = 128.0;
CGFloat ratio = 1.0;
NSAttributedString *originalAttributedString = [self copy];
CGFloat lastRatioWhichFits = 0.02;
while (true) {
[self scaleFontSizeWithRatio:ratio
minimumFontSize:minimumFontSize
maximumFontSize:maximumFontSize];
RCTTextSizeComparisonOptions comparsion =
[self compareToSize:size thresholdRatio:0.01];
if (
(comparsion & RCTTextSizeComparisonWithinRange) &&
(comparsion & RCTTextSizeComparisonSmaller)
) {
return;
} else if (comparsion & RCTTextSizeComparisonSmaller) {
bottomRatio = ratio;
lastRatioWhichFits = ratio;
} else {
topRatio = ratio;
}
ratio = (topRatio + bottomRatio) / 2.0;
CGFloat kRatioThreshold = 0.005;
if (
ABS(topRatio - bottomRatio) < kRatioThreshold ||
ABS(topRatio - ratio) < kRatioThreshold ||
ABS(bottomRatio - ratio) < kRatioThreshold
) {
[self replaceCharactersInRange:(NSRange){0, self.length}
withAttributedString:originalAttributedString];
[self scaleFontSizeWithRatio:lastRatioWhichFits
minimumFontSize:minimumFontSize
maximumFontSize:maximumFontSize];
return;
}
[self replaceCharactersInRange:(NSRange){0, self.length}
withAttributedString:originalAttributedString];
}
}
- (RCTTextSizeComparisonOptions)compareToSize:(CGSize)size thresholdRatio:(CGFloat)thresholdRatio
{
NSLayoutManager *layoutManager = self.layoutManagers.firstObject;
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
[layoutManager ensureLayoutForTextContainer:textContainer];
// Does it fit the text container?
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
NSRange truncatedGlyphRange = [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:glyphRange.length - 1];
if (truncatedGlyphRange.location != NSNotFound) {
return RCTTextSizeComparisonLarger;
}
CGSize measuredSize = [layoutManager usedRectForTextContainer:textContainer].size;
// Does it fit the size?
BOOL fitsSize =
size.width >= measuredSize.width &&
size.height >= measuredSize.height;
CGSize thresholdSize = (CGSize){
size.width * thresholdRatio,
size.height * thresholdRatio,
};
RCTTextSizeComparisonOptions result = 0;
result |= (fitsSize) ? RCTTextSizeComparisonSmaller : RCTTextSizeComparisonLarger;
if (ABS(measuredSize.width - size.width) < thresholdSize.width) {
result = result | RCTTextSizeComparisonWithinRange;
}
return result;
}
- (void)scaleFontSizeWithRatio:(CGFloat)ratio
minimumFontSize:(CGFloat)minimumFontSize
maximumFontSize:(CGFloat)maximumFontSize
{
[self beginEditing];
[self enumerateAttribute:NSFontAttributeName
inRange:(NSRange){0, self.length}
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
usingBlock:
^(UIFont *_Nullable font, NSRange range, BOOL *_Nonnull stop) {
if (!font) {
return;
}
CGFloat fontSize = MAX(MIN(font.pointSize * ratio, maximumFontSize), minimumFontSize);
[self addAttribute:NSFontAttributeName
value:[font fontWithSize:fontSize]
range:range];
}
];
[self endEditing];
}
@end

View File

@ -8,49 +8,22 @@
*/
#import <React/RCTShadowView.h>
#import <React/RCTTextDecorationLineType.h>
typedef NS_ENUM(NSInteger, RCTSizeComparison)
{
RCTSizeTooLarge,
RCTSizeTooSmall,
RCTSizeWithinRange,
};
#import "RCTBaseTextShadowView.h"
NS_ASSUME_NONNULL_BEGIN
extern NSString *const RCTIsHighlightedAttributeName;
extern NSString *const RCTReactTagAttributeName;
@interface RCTTextShadowView : RCTBaseTextShadowView
@interface RCTTextShadowView : RCTShadowView
- (instancetype)initWithBridge:(RCTBridge *)bridge;
@property (nonatomic, strong) UIColor *color;
@property (nonatomic, strong) UIColor *backgroundColor;
@property (nonatomic, copy) NSString *fontFamily;
@property (nonatomic, assign) CGFloat fontSize;
@property (nonatomic, copy) NSString *fontWeight;
@property (nonatomic, copy) NSString *fontStyle;
@property (nonatomic, copy) NSArray *fontVariant;
@property (nonatomic, assign) BOOL isHighlighted;
@property (nonatomic, assign) CGFloat letterSpacing;
@property (nonatomic, assign) CGFloat lineHeight;
@property (nonatomic, assign) NSUInteger numberOfLines;
@property (nonatomic, assign) NSLineBreakMode ellipsizeMode;
@property (nonatomic, assign) CGSize shadowOffset;
@property (nonatomic, assign) NSTextAlignment textAlign;
@property (nonatomic, assign) NSWritingDirection writingDirection;
@property (nonatomic, strong) UIColor *textDecorationColor;
@property (nonatomic, assign) NSUnderlineStyle textDecorationStyle;
@property (nonatomic, assign) RCTTextDecorationLineType textDecorationLine;
@property (nonatomic, assign) CGFloat fontSizeMultiplier;
@property (nonatomic, assign) BOOL allowFontScaling;
@property (nonatomic, assign) CGFloat opacity;
@property (nonatomic, assign) CGSize textShadowOffset;
@property (nonatomic, assign) CGFloat textShadowRadius;
@property (nonatomic, strong) UIColor *textShadowColor;
@property (nonatomic, assign) NSInteger maximumNumberOfLines;
@property (nonatomic, assign) NSLineBreakMode lineBreakMode;
@property (nonatomic, assign) BOOL adjustsFontSizeToFit;
@property (nonatomic, assign) CGFloat minimumFontScale;
@property (nonatomic, assign) BOOL selectable;
- (void)recomputeText;
- (void)uiManagerWillPerformMounting;
@end
NS_ASSUME_NONNULL_END

View File

@ -9,678 +9,336 @@
#import "RCTTextShadowView.h"
#import <React/RCTAccessibilityManager.h>
#import <React/RCTBridge.h>
#import <React/RCTConvert.h>
#import <React/RCTFont.h>
#import <React/RCTLog.h>
#import <React/RCTShadowView+Layout.h>
#import <React/RCTUIManager.h>
#import <React/RCTUtils.h>
#import <yoga/Yoga.h>
#import "RCTRawTextShadowView.h"
#import "NSTextStorage+FontScaling.h"
#import "RCTTextView.h"
#import "RCTMultilineTextInputView.h"
NSString *const RCTIsHighlightedAttributeName = @"IsHighlightedAttributeName";
NSString *const RCTReactTagAttributeName = @"ReactTagAttributeName";
static NSString *const kShadowViewAttributeName = @"RCTShadowViewAttributeName";
static CGFloat const kAutoSizeWidthErrorMargin = 0.05f;
static CGFloat const kAutoSizeHeightErrorMargin = 0.025f;
static CGFloat const kAutoSizeGranularity = 0.001f;
@implementation RCTTextShadowView
{
NSTextStorage *_cachedTextStorage;
CGFloat _cachedTextStorageWidth;
CGFloat _cachedTextStorageWidthMode;
NSAttributedString *_cachedAttributedString;
CGFloat _effectiveLetterSpacing;
UIUserInterfaceLayoutDirection _cachedLayoutDirection;
__weak RCTBridge *_bridge;
BOOL _needsUpdateView;
NSMapTable<id, NSTextStorage *> *_cachedTextStorages;
}
static YGSize RCTMeasure(YGNodeRef node, float width, YGMeasureMode widthMode, float height, YGMeasureMode heightMode)
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
RCTTextShadowView *shadowText = (__bridge RCTTextShadowView *)YGNodeGetContext(node);
NSTextStorage *textStorage = [shadowText buildTextStorageForWidth:width widthMode:widthMode];
[shadowText calculateTextFrame:textStorage];
NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject;
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
CGSize computedSize = [layoutManager usedRectForTextContainer:textContainer].size;
YGSize result;
result.width = RCTCeilPixelValue(computedSize.width);
if (shadowText->_effectiveLetterSpacing < 0) {
result.width -= shadowText->_effectiveLetterSpacing;
if (self = [super init]) {
_bridge = bridge;
_cachedTextStorages = [NSMapTable strongToStrongObjectsMapTable];
_needsUpdateView = YES;
YGNodeSetMeasureFunc(self.yogaNode, RCTTextShadowViewMeasure);
}
result.height = RCTCeilPixelValue(computedSize.height);
return result;
}
- (instancetype)init
{
if ((self = [super init])) {
_fontSize = NAN;
_letterSpacing = NAN;
_isHighlighted = NO;
_textDecorationStyle = NSUnderlineStyleSingle;
_opacity = 1.0;
_cachedTextStorageWidth = -1;
_cachedTextStorageWidthMode = -1;
_fontSizeMultiplier = 1.0;
_textAlign = NSTextAlignmentNatural;
_writingDirection = NSWritingDirectionNatural;
_cachedLayoutDirection = UIUserInterfaceLayoutDirectionLeftToRight;
YGNodeSetMeasureFunc(self.yogaNode, RCTMeasure);
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(contentSizeMultiplierDidChange:)
name:RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotification
object:nil];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (NSString *)description
{
NSString *superDescription = super.description;
return [[superDescription substringToIndex:superDescription.length - 1] stringByAppendingFormat:@"; text: %@>", [self attributedString].string];
}
- (BOOL)isYogaLeafNode
{
return YES;
}
- (void)contentSizeMultiplierDidChange:(NSNotification *)note
- (void)dirtyLayout
{
[super dirtyLayout];
YGNodeMarkDirty(self.yogaNode);
[self dirtyText];
[self invalidateCache];
}
- (NSDictionary<NSString *, id> *)processUpdatedProperties:(NSMutableSet<RCTApplierBlock> *)applierBlocks
parentProperties:(NSDictionary<NSString *, id> *)parentProperties
- (void)invalidateCache
{
if ([[self reactSuperview] isKindOfClass:[RCTTextShadowView class]]) {
return parentProperties;
[_cachedTextStorages removeAllObjects];
_needsUpdateView = YES;
}
#pragma mark - RCTUIManagerObserver
- (void)uiManagerWillPerformMounting
{
if (YGNodeIsDirty(self.yogaNode)) {
return;
}
parentProperties = [super processUpdatedProperties:applierBlocks
parentProperties:parentProperties];
if (!_needsUpdateView) {
return;
}
_needsUpdateView = NO;
CGFloat availableWidth = self.availableSize.width;
NSNumber *parentTag = [[self reactSuperview] reactTag];
NSTextStorage *textStorage = [self buildTextStorageForWidth:availableWidth widthMode:YGMeasureModeExactly];
CGRect textFrame = [self calculateTextFrame:textStorage];
BOOL selectable = _selectable;
[applierBlocks addObject:^(NSDictionary<NSNumber *, UIView *> *viewRegistry) {
RCTTextView *view = (RCTTextView *)viewRegistry[self.reactTag];
view.textFrame = textFrame;
view.textStorage = textStorage;
view.selectable = selectable;
CGRect contentFrame = self.contentFrame;
NSTextStorage *textStorage = [self textStorageAndLayoutManagerThatFitsSize:self.contentFrame.size
exclusiveOwnership:YES];
/**
* NOTE: this logic is included to support rich text editing inside multiline
* `<TextInput>` controls. It is required in order to ensure that the
* textStorage (aka attributed string) is copied over from the RCTTextShadowView
* to the RCTTextView view in time to be used to update the editable text content.
* TODO: we should establish a delegate relationship betweeen RCTMultilineTextInputView
* and its contaned RCTTextView element when they get inserted and get rid of this
*/
UIView *parentView = viewRegistry[parentTag];
if ([parentView respondsToSelector:@selector(performTextUpdate)]) {
[(RCTMultilineTextInputView *)parentView performTextUpdate];
NSNumber *tag = self.reactTag;
NSMutableArray<NSNumber *> *descendantViewTags = [NSMutableArray new];
[textStorage enumerateAttribute:RCTBaseTextShadowViewEmbeddedShadowViewAttributeName
inRange:NSMakeRange(0, textStorage.length)
options:0
usingBlock:
^(RCTShadowView *shadowView, NSRange range, __unused BOOL *stop) {
if (!shadowView) {
return;
}
[descendantViewTags addObject:shadowView.reactTag];
}
];
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
RCTTextView *textView = (RCTTextView *)viewRegistry[tag];
if (!textView) {
return;
}
}];
return parentProperties;
NSMutableArray<UIView *> *descendantViews =
[NSMutableArray arrayWithCapacity:descendantViewTags.count];
[descendantViewTags enumerateObjectsUsingBlock:^(NSNumber *_Nonnull descendantViewTag, NSUInteger index, BOOL *_Nonnull stop) {
UIView *descendantView = viewRegistry[descendantViewTag];
if (!descendantView) {
return;
}
[descendantViews addObject:descendantView];
}];
// Removing all references to Shadow Views to avoid unnececery retainning.
[textStorage removeAttribute:RCTBaseTextShadowViewEmbeddedShadowViewAttributeName range:NSMakeRange(0, textStorage.length)];
[textView setTextStorage:textStorage
contentFrame:contentFrame
descendantViews:descendantViews];
}];
}
- (void)applyLayoutNode:(YGNodeRef)node
viewsWithNewFrame:(NSMutableSet<RCTShadowView *> *)viewsWithNewFrame
absolutePosition:(CGPoint)absolutePosition
- (void)postprocessAttributedText:(NSMutableAttributedString *)attributedText
{
[super applyLayoutNode:node viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition];
[self dirtyPropagation];
__block CGFloat maximumLineHeight = 0;
[attributedText enumerateAttribute:NSParagraphStyleAttributeName
inRange:NSMakeRange(0, attributedText.length)
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
usingBlock:
^(NSParagraphStyle *paragraphStyle, __unused NSRange range, __unused BOOL *stop) {
if (!paragraphStyle) {
return;
}
maximumLineHeight = MAX(paragraphStyle.maximumLineHeight, maximumLineHeight);
}
];
if (maximumLineHeight == 0) {
// `lineHeight` was not specified, nothing to do.
return;
}
[attributedText beginEditing];
[attributedText enumerateAttribute:NSFontAttributeName
inRange:NSMakeRange(0, attributedText.length)
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
usingBlock:
^(UIFont *font, NSRange range, __unused BOOL *stop) {
if (!font) {
return;
}
if (maximumLineHeight <= font.lineHeight) {
return;
}
CGFloat baseLineOffset = maximumLineHeight / 2.0 - font.lineHeight / 2.0;
[attributedText addAttribute:NSBaselineOffsetAttributeName
value:@(baseLineOffset)
range:range];
}
];
[attributedText endEditing];
}
- (NSAttributedString *)attributedTextWithMeasuredAttachmentsThatFitSize:(CGSize)size
{
NSMutableAttributedString *attributedText =
[[NSMutableAttributedString alloc] initWithAttributedString:[self attributedTextWithBaseTextAttributes:nil]];
[attributedText beginEditing];
[attributedText enumerateAttribute:RCTBaseTextShadowViewEmbeddedShadowViewAttributeName
inRange:NSMakeRange(0, attributedText.length)
options:0
usingBlock:
^(RCTShadowView *shadowView, NSRange range, __unused BOOL *stop) {
if (!shadowView) {
return;
}
CGSize fittingSize = [shadowView sizeThatFitsMinimumSize:CGSizeZero
maximumSize:size];
NSTextAttachment *attachment = [NSTextAttachment new];
attachment.bounds = (CGRect){CGPointZero, fittingSize};
[attributedText addAttribute:NSAttachmentAttributeName value:attachment range:range];
}
];
[attributedText endEditing];
return [attributedText copy];
}
- (NSTextStorage *)textStorageAndLayoutManagerThatFitsSize:(CGSize)size
exclusiveOwnership:(BOOL)exclusiveOwnership
{
NSValue *key = [NSValue valueWithCGSize:size];
NSTextStorage *cachedTextStorage = [_cachedTextStorages objectForKey:key];
if (cachedTextStorage) {
if (exclusiveOwnership) {
[_cachedTextStorages removeObjectForKey:key];
}
return cachedTextStorage;
}
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:size];
textContainer.lineFragmentPadding = 0.0; // Note, the default value is 5.
textContainer.lineBreakMode =
_maximumNumberOfLines > 0 ? _lineBreakMode : NSLineBreakByClipping;
textContainer.maximumNumberOfLines = _maximumNumberOfLines;
NSLayoutManager *layoutManager = [NSLayoutManager new];
[layoutManager addTextContainer:textContainer];
NSTextStorage *textStorage =
[[NSTextStorage alloc] initWithAttributedString:[self attributedTextWithMeasuredAttachmentsThatFitSize:size]];
[self postprocessAttributedText:textStorage];
[textStorage addLayoutManager:layoutManager];
if (_adjustsFontSizeToFit) {
CGFloat minimumFontSize =
MAX(_minimumFontScale * (self.textAttributes.effectiveFont.pointSize), 4.0);
[textStorage scaleFontSizeToFitSize:size
minimumFontSize:minimumFontSize
maximumFontSize:72.0];
}
if (!exclusiveOwnership) {
[_cachedTextStorages setObject:textStorage forKey:key];
}
return textStorage;
}
- (void)applyLayoutWithFrame:(CGRect)frame
layoutDirection:(UIUserInterfaceLayoutDirection)layoutDirection
viewsWithUpdatedLayout:(NSMutableSet<RCTShadowView *> *)viewsWithUpdatedLayout
absolutePosition:(CGPoint)absolutePosition
{
if (self.textAttributes.layoutDirection != layoutDirection) {
self.textAttributes.layoutDirection = layoutDirection;
[self invalidateCache];
}
[super applyLayoutWithFrame:frame
layoutDirection:layoutDirection
viewsWithUpdatedLayout:viewsWithUpdatedLayout
absolutePosition:absolutePosition];
}
- (void)applyLayoutToChildren:(YGNodeRef)node
viewsWithNewFrame:(NSMutableSet<RCTShadowView *> *)viewsWithNewFrame
absolutePosition:(CGPoint)absolutePosition
{
// Run layout on subviews.
CGFloat availableWidth = self.availableSize.width;
NSTextStorage *textStorage = [self buildTextStorageForWidth:availableWidth widthMode:YGMeasureModeExactly];
NSTextStorage *textStorage =
[self textStorageAndLayoutManagerThatFitsSize:self.availableSize
exclusiveOwnership:NO];
NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject;
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL];
[layoutManager.textStorage enumerateAttribute:kShadowViewAttributeName inRange:characterRange options:0 usingBlock:^(RCTShadowView *child, NSRange range, BOOL *_) {
if (child) {
YGNodeRef childNode = child.yogaNode;
float width = YGNodeStyleGetWidth(childNode).value;
float height = YGNodeStyleGetHeight(childNode).value;
if (YGFloatIsUndefined(width) || YGFloatIsUndefined(height)) {
RCTLogError(@"Views nested within a <Text> must have a width and height");
NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange
actualGlyphRange:NULL];
[textStorage enumerateAttribute:RCTBaseTextShadowViewEmbeddedShadowViewAttributeName
inRange:characterRange
options:0
usingBlock:
^(RCTShadowView *shadowView, NSRange range, BOOL *stop) {
if (!shadowView) {
return;
}
CGRect glyphRect = [layoutManager boundingRectForGlyphRange:range
inTextContainer:textContainer];
NSTextAttachment *attachment =
[textStorage attribute:NSAttachmentAttributeName atIndex:range.location effectiveRange:nil];
CGSize attachmentSize = attachment.bounds.size;
UIFont *font = [textStorage attribute:NSFontAttributeName atIndex:range.location effectiveRange:nil];
CGRect glyphRect = [layoutManager boundingRectForGlyphRange:range inTextContainer:textContainer];
CGRect childFrame = {{
CGRect frame = {{
RCTRoundPixelValue(glyphRect.origin.x),
RCTRoundPixelValue(glyphRect.origin.y + glyphRect.size.height - height + font.descender)
RCTRoundPixelValue(glyphRect.origin.y + glyphRect.size.height - attachmentSize.height + font.descender)
}, {
RCTRoundPixelValue(width),
RCTRoundPixelValue(height)
RCTRoundPixelValue(attachmentSize.width),
RCTRoundPixelValue(attachmentSize.height)
}};
NSRange truncatedGlyphRange = [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:range.location];
BOOL childIsTruncated = NSIntersectionRange(range, truncatedGlyphRange).length != 0;
UIUserInterfaceLayoutDirection layoutDirection = self.textAttributes.layoutDirection;
[child collectUpdatedFrames:viewsWithNewFrame
withFrame:childFrame
hidden:childIsTruncated
absolutePosition:absolutePosition];
YGNodeCalculateLayout(
shadowView.yogaNode,
frame.size.width,
frame.size.height,
layoutDirection == UIUserInterfaceLayoutDirectionLeftToRight ? YGDirectionLTR : YGDirectionRTL);
[shadowView applyLayoutWithFrame:frame
layoutDirection:layoutDirection
viewsWithUpdatedLayout:viewsWithNewFrame
absolutePosition:absolutePosition];
}
}];
];
}
- (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width widthMode:(YGMeasureMode)widthMode
static YGSize RCTTextShadowViewMeasure(YGNodeRef node, float width, YGMeasureMode widthMode, float height, YGMeasureMode heightMode)
{
if (
_cachedTextStorage &&
(width == _cachedTextStorageWidth || (isnan(width) && isnan(_cachedTextStorageWidth))) &&
widthMode == _cachedTextStorageWidthMode &&
_cachedLayoutDirection == self.layoutDirection
) {
return _cachedTextStorage;
}
NSLayoutManager *layoutManager = [NSLayoutManager new];
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:self.attributedString];
[textStorage addLayoutManager:layoutManager];
NSTextContainer *textContainer = [NSTextContainer new];
textContainer.lineFragmentPadding = 0.0;
if (_numberOfLines > 0) {
textContainer.lineBreakMode = _ellipsizeMode;
} else {
textContainer.lineBreakMode = NSLineBreakByClipping;
}
textContainer.maximumNumberOfLines = _numberOfLines;
textContainer.size = (CGSize){
widthMode == YGMeasureModeUndefined || isnan(width) ? CGFLOAT_MAX : width,
CGFLOAT_MAX
CGSize maximumSize = (CGSize){
widthMode == YGMeasureModeUndefined ? CGFLOAT_MAX : RCTCoreGraphicsFloatFromYogaFloat(width),
heightMode == YGMeasureModeUndefined ? CGFLOAT_MAX : RCTCoreGraphicsFloatFromYogaFloat(height),
};
[layoutManager addTextContainer:textContainer];
RCTTextShadowView *shadowTextView = (__bridge RCTTextShadowView *)YGNodeGetContext(node);
NSTextStorage *textStorage =
[shadowTextView textStorageAndLayoutManagerThatFitsSize:maximumSize
exclusiveOwnership:NO];
NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject;
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
[layoutManager ensureLayoutForTextContainer:textContainer];
CGSize size = [layoutManager usedRectForTextContainer:textContainer].size;
_cachedTextStorageWidth = width;
_cachedTextStorageWidthMode = widthMode;
_cachedTextStorage = textStorage;
return textStorage;
}
- (void)dirtyText
{
[super dirtyText];
_cachedTextStorage = nil;
}
- (void)recomputeText
{
[self attributedString];
[self setTextComputed];
[self dirtyPropagation];
}
- (NSAttributedString *)attributedString
{
return [self _attributedStringWithFontFamily:nil
fontSize:nil
fontWeight:nil
fontStyle:nil
letterSpacing:nil
useBackgroundColor:NO
foregroundColor:self.color ?: [UIColor blackColor]
backgroundColor:self.backgroundColor
opacity:self.opacity];
}
- (NSAttributedString *)_attributedStringWithFontFamily:(NSString *)fontFamily
fontSize:(NSNumber *)fontSize
fontWeight:(NSString *)fontWeight
fontStyle:(NSString *)fontStyle
letterSpacing:(NSNumber *)letterSpacing
useBackgroundColor:(BOOL)useBackgroundColor
foregroundColor:(UIColor *)foregroundColor
backgroundColor:(UIColor *)backgroundColor
opacity:(CGFloat)opacity
{
if (
![self isTextDirty] &&
_cachedAttributedString &&
_cachedLayoutDirection == self.layoutDirection
) {
return _cachedAttributedString;
CGFloat letterSpacing = shadowTextView.textAttributes.letterSpacing;
if (!isnan(letterSpacing) && letterSpacing < 0) {
size.width -= letterSpacing;
}
_cachedLayoutDirection = self.layoutDirection;
size = (CGSize){
MIN(RCTCeilPixelValue(size.width), maximumSize.width),
MIN(RCTCeilPixelValue(size.height), maximumSize.height)
};
if (_fontSize && !isnan(_fontSize)) {
fontSize = @(_fontSize);
}
if (_fontWeight) {
fontWeight = _fontWeight;
}
if (_fontStyle) {
fontStyle = _fontStyle;
}
if (_fontFamily) {
fontFamily = _fontFamily;
}
if (!isnan(_letterSpacing)) {
letterSpacing = @(_letterSpacing);
}
_effectiveLetterSpacing = letterSpacing.doubleValue;
UIFont *font = [RCTFont updateFont:nil
withFamily:fontFamily
size:fontSize
weight:fontWeight
style:fontStyle
variant:_fontVariant
scaleMultiplier:_allowFontScaling ? _fontSizeMultiplier : 1.0];
CGFloat heightOfTallestSubview = 0.0;
NSMutableAttributedString *attributedString = [NSMutableAttributedString new];
for (RCTShadowView *child in [self reactSubviews]) {
if ([child isKindOfClass:[RCTTextShadowView class]]) {
RCTTextShadowView *shadowText = (RCTTextShadowView *)child;
[attributedString appendAttributedString:
[shadowText _attributedStringWithFontFamily:fontFamily
fontSize:fontSize
fontWeight:fontWeight
fontStyle:fontStyle
letterSpacing:letterSpacing
useBackgroundColor:YES
foregroundColor:shadowText.color ?: foregroundColor
backgroundColor:shadowText.backgroundColor ?: backgroundColor
opacity:opacity * shadowText.opacity]];
[child setTextComputed];
} else if ([child isKindOfClass:[RCTRawTextShadowView class]]) {
RCTRawTextShadowView *shadowRawText = (RCTRawTextShadowView *)child;
[attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:shadowRawText.text ?: @""]];
[child setTextComputed];
} else {
float width = YGNodeStyleGetWidth(child.yogaNode).value;
float height = YGNodeStyleGetHeight(child.yogaNode).value;
if (YGFloatIsUndefined(width) || YGFloatIsUndefined(height)) {
RCTLogError(@"Views nested within a <Text> must have a width and height");
}
NSTextAttachment *attachment = [NSTextAttachment new];
attachment.bounds = (CGRect){CGPointZero, {width, height}};
NSMutableAttributedString *attachmentString = [NSMutableAttributedString new];
[attachmentString appendAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]];
[attachmentString addAttribute:kShadowViewAttributeName value:child range:(NSRange){0, attachmentString.length}];
[attributedString appendAttributedString:attachmentString];
if (height > heightOfTallestSubview) {
heightOfTallestSubview = height;
}
// Don't call setTextComputed on this child. RCTTextViewManager takes care of
// processing inline UIViews.
}
}
[self _addAttribute:NSForegroundColorAttributeName
withValue:[foregroundColor colorWithAlphaComponent:CGColorGetAlpha(foregroundColor.CGColor) * opacity]
toAttributedString:attributedString];
if (_isHighlighted) {
[self _addAttribute:RCTIsHighlightedAttributeName withValue:@YES toAttributedString:attributedString];
}
if (useBackgroundColor && backgroundColor) {
[self _addAttribute:NSBackgroundColorAttributeName
withValue:[backgroundColor colorWithAlphaComponent:CGColorGetAlpha(backgroundColor.CGColor) * opacity]
toAttributedString:attributedString];
}
[self _addAttribute:NSFontAttributeName withValue:font toAttributedString:attributedString];
[self _addAttribute:NSKernAttributeName withValue:letterSpacing toAttributedString:attributedString];
[self _addAttribute:RCTReactTagAttributeName withValue:self.reactTag toAttributedString:attributedString];
[self _setParagraphStyleOnAttributedString:attributedString
fontLineHeight:font.lineHeight
heightOfTallestSubview:heightOfTallestSubview];
// create a non-mutable attributedString for use by the Text system which avoids copies down the line
_cachedAttributedString = [[NSAttributedString alloc] initWithAttributedString:attributedString];
YGNodeMarkDirty(self.yogaNode);
return _cachedAttributedString;
}
- (void)_addAttribute:(NSString *)attribute withValue:(id)attributeValue toAttributedString:(NSMutableAttributedString *)attributedString
{
[attributedString enumerateAttribute:attribute inRange:NSMakeRange(0, attributedString.length) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) {
if (!value && attributeValue) {
[attributedString addAttribute:attribute value:attributeValue range:range];
}
}];
}
/*
* LineHeight works the same way line-height works in the web: if children and self have
* varying lineHeights, we simply take the max.
*/
- (void)_setParagraphStyleOnAttributedString:(NSMutableAttributedString *)attributedString
fontLineHeight:(CGFloat)fontLineHeight
heightOfTallestSubview:(CGFloat)heightOfTallestSubview
{
__block BOOL hasParagraphStyle = NO;
if (_lineHeight != 0.0 || _textAlign != NSTextAlignmentNatural) {
hasParagraphStyle = YES;
}
CGFloat fontSizeMultiplier = _allowFontScaling ? _fontSizeMultiplier : 1.0;
// Text line height
__block float compoundLineHeight = _lineHeight * fontSizeMultiplier;
// Checking for `maximumLineHeight` on each of our children and updating `compoundLineHeight` with the maximum value on the go.
[attributedString enumerateAttribute:NSParagraphStyleAttributeName
inRange:(NSRange){0, attributedString.length}
options:0
usingBlock:^(NSParagraphStyle *paragraphStyle, NSRange range, BOOL *stop) {
if (!paragraphStyle) {
return;
}
hasParagraphStyle = YES;
compoundLineHeight = MAX(compoundLineHeight, paragraphStyle.maximumLineHeight);
}];
compoundLineHeight = MAX(round(compoundLineHeight), ceilf(heightOfTallestSubview));
// Text alignment
NSTextAlignment textAlign = _textAlign;
if (textAlign == NSTextAlignmentRight || textAlign == NSTextAlignmentLeft) {
if (_cachedLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) {
if (textAlign == NSTextAlignmentRight) {
textAlign = NSTextAlignmentLeft;
} else {
textAlign = NSTextAlignmentRight;
}
}
}
if (hasParagraphStyle) {
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.alignment = textAlign;
paragraphStyle.baseWritingDirection = _writingDirection;
paragraphStyle.minimumLineHeight = compoundLineHeight;
paragraphStyle.maximumLineHeight = compoundLineHeight;
[attributedString addAttribute:NSParagraphStyleAttributeName
value:paragraphStyle
range:(NSRange){0, attributedString.length}];
if (compoundLineHeight > fontLineHeight) {
[attributedString addAttribute:NSBaselineOffsetAttributeName
value:@(compoundLineHeight / 2 - fontLineHeight / 2)
range:(NSRange){0, attributedString.length}];
}
}
// Text decoration
if (_textDecorationLine == RCTTextDecorationLineTypeUnderline ||
_textDecorationLine == RCTTextDecorationLineTypeUnderlineStrikethrough) {
[self _addAttribute:NSUnderlineStyleAttributeName withValue:@(_textDecorationStyle)
toAttributedString:attributedString];
}
if (_textDecorationLine == RCTTextDecorationLineTypeStrikethrough ||
_textDecorationLine == RCTTextDecorationLineTypeUnderlineStrikethrough){
[self _addAttribute:NSStrikethroughStyleAttributeName withValue:@(_textDecorationStyle)
toAttributedString:attributedString];
}
if (_textDecorationColor) {
[self _addAttribute:NSStrikethroughColorAttributeName withValue:_textDecorationColor
toAttributedString:attributedString];
[self _addAttribute:NSUnderlineColorAttributeName withValue:_textDecorationColor
toAttributedString:attributedString];
}
// Text shadow
if (!CGSizeEqualToSize(_textShadowOffset, CGSizeZero)) {
NSShadow *shadow = [NSShadow new];
shadow.shadowOffset = _textShadowOffset;
shadow.shadowBlurRadius = _textShadowRadius;
shadow.shadowColor = _textShadowColor;
[self _addAttribute:NSShadowAttributeName withValue:shadow toAttributedString:attributedString];
}
}
#pragma mark Autosizing
- (CGRect)calculateTextFrame:(NSTextStorage *)textStorage
{
CGRect textFrame = UIEdgeInsetsInsetRect((CGRect){CGPointZero, self.frame.size},
self.compoundInsets);
if (_adjustsFontSizeToFit) {
textFrame = [self updateStorage:textStorage toFitFrame:textFrame];
}
return textFrame;
}
- (CGRect)updateStorage:(NSTextStorage *)textStorage toFitFrame:(CGRect)frame
{
BOOL fits = [self attemptScale:1.0f
inStorage:textStorage
forFrame:frame];
CGSize requiredSize;
if (!fits) {
requiredSize = [self calculateOptimumScaleInFrame:frame
forStorage:textStorage
minScale:self.minimumFontScale
maxScale:1.0
prevMid:INT_MAX];
} else {
requiredSize = [self calculateSize:textStorage];
}
// Vertically center draw position for new text sizing.
frame.origin.y = self.compoundInsets.top + RCTRoundPixelValue((CGRectGetHeight(frame) - requiredSize.height) / 2.0f);
return frame;
}
- (CGSize)calculateOptimumScaleInFrame:(CGRect)frame
forStorage:(NSTextStorage *)textStorage
minScale:(CGFloat)minScale
maxScale:(CGFloat)maxScale
prevMid:(CGFloat)prevMid
{
CGFloat midScale = (minScale + maxScale) / 2.0f;
if (round((prevMid / kAutoSizeGranularity)) == round((midScale / kAutoSizeGranularity))) {
//Bail because we can't meet error margin.
return [self calculateSize:textStorage];
} else {
RCTSizeComparison comparison = [self attemptScale:midScale
inStorage:textStorage
forFrame:frame];
if (comparison == RCTSizeWithinRange) {
return [self calculateSize:textStorage];
} else if (comparison == RCTSizeTooLarge) {
return [self calculateOptimumScaleInFrame:frame
forStorage:textStorage
minScale:minScale
maxScale:midScale - kAutoSizeGranularity
prevMid:midScale];
} else {
return [self calculateOptimumScaleInFrame:frame
forStorage:textStorage
minScale:midScale + kAutoSizeGranularity
maxScale:maxScale
prevMid:midScale];
}
}
}
- (RCTSizeComparison)attemptScale:(CGFloat)scale
inStorage:(NSTextStorage *)textStorage
forFrame:(CGRect)frame
{
NSLayoutManager *layoutManager = [textStorage.layoutManagers firstObject];
NSTextContainer *textContainer = [layoutManager.textContainers firstObject];
NSRange glyphRange = NSMakeRange(0, textStorage.length);
[textStorage beginEditing];
[textStorage enumerateAttribute:NSFontAttributeName
inRange:glyphRange
options:0
usingBlock:^(UIFont *font, NSRange range, BOOL *stop)
{
if (font) {
UIFont *originalFont = [self.attributedString attribute:NSFontAttributeName
atIndex:range.location
effectiveRange:&range];
UIFont *newFont = [font fontWithSize:originalFont.pointSize * scale];
[textStorage removeAttribute:NSFontAttributeName range:range];
[textStorage addAttribute:NSFontAttributeName value:newFont range:range];
}
}];
[textStorage endEditing];
NSInteger linesRequired = [self numberOfLinesRequired:[textStorage.layoutManagers firstObject]];
CGSize requiredSize = [self calculateSize:textStorage];
BOOL fitSize = requiredSize.height <= CGRectGetHeight(frame) &&
requiredSize.width <= CGRectGetWidth(frame);
BOOL fitLines = linesRequired <= textContainer.maximumNumberOfLines ||
textContainer.maximumNumberOfLines == 0;
if (fitLines && fitSize) {
if ((requiredSize.width + (CGRectGetWidth(frame) * kAutoSizeWidthErrorMargin)) > CGRectGetWidth(frame) &&
(requiredSize.height + (CGRectGetHeight(frame) * kAutoSizeHeightErrorMargin)) > CGRectGetHeight(frame))
{
return RCTSizeWithinRange;
} else {
return RCTSizeTooSmall;
}
} else {
return RCTSizeTooLarge;
}
}
// Via Apple Text Layout Programming Guide
// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/TextLayout/Tasks/CountLines.html
- (NSInteger)numberOfLinesRequired:(NSLayoutManager *)layoutManager
{
NSInteger numberOfLines, index, numberOfGlyphs = [layoutManager numberOfGlyphs];
NSRange lineRange;
for (numberOfLines = 0, index = 0; index < numberOfGlyphs; numberOfLines++){
(void) [layoutManager lineFragmentRectForGlyphAtIndex:index
effectiveRange:&lineRange];
index = NSMaxRange(lineRange);
}
return numberOfLines;
}
// Via Apple Text Layout Programming Guide
//https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/TextLayout/Tasks/StringHeight.html
- (CGSize)calculateSize:(NSTextStorage *)storage
{
NSLayoutManager *layoutManager = [storage.layoutManagers firstObject];
NSTextContainer *textContainer = [layoutManager.textContainers firstObject];
[textContainer setLineBreakMode:NSLineBreakByWordWrapping];
NSInteger maxLines = [textContainer maximumNumberOfLines];
[textContainer setMaximumNumberOfLines:0];
(void) [layoutManager glyphRangeForTextContainer:textContainer];
CGSize requiredSize = [layoutManager usedRectForTextContainer:textContainer].size;
[textContainer setMaximumNumberOfLines:maxLines];
return requiredSize;
}
#define RCT_TEXT_PROPERTY(setProp, ivar, type) \
- (void)set##setProp:(type)value; \
{ \
ivar = value; \
[self dirtyText]; \
}
RCT_TEXT_PROPERTY(AdjustsFontSizeToFit, _adjustsFontSizeToFit, BOOL)
RCT_TEXT_PROPERTY(Color, _color, UIColor *)
RCT_TEXT_PROPERTY(FontFamily, _fontFamily, NSString *)
RCT_TEXT_PROPERTY(FontSize, _fontSize, CGFloat)
RCT_TEXT_PROPERTY(FontWeight, _fontWeight, NSString *)
RCT_TEXT_PROPERTY(FontStyle, _fontStyle, NSString *)
RCT_TEXT_PROPERTY(FontVariant, _fontVariant, NSArray *)
RCT_TEXT_PROPERTY(IsHighlighted, _isHighlighted, BOOL)
RCT_TEXT_PROPERTY(LetterSpacing, _letterSpacing, CGFloat)
RCT_TEXT_PROPERTY(LineHeight, _lineHeight, CGFloat)
RCT_TEXT_PROPERTY(NumberOfLines, _numberOfLines, NSUInteger)
RCT_TEXT_PROPERTY(EllipsizeMode, _ellipsizeMode, NSLineBreakMode)
RCT_TEXT_PROPERTY(TextAlign, _textAlign, NSTextAlignment)
RCT_TEXT_PROPERTY(TextDecorationColor, _textDecorationColor, UIColor *);
RCT_TEXT_PROPERTY(TextDecorationLine, _textDecorationLine, RCTTextDecorationLineType);
RCT_TEXT_PROPERTY(TextDecorationStyle, _textDecorationStyle, NSUnderlineStyle);
RCT_TEXT_PROPERTY(WritingDirection, _writingDirection, NSWritingDirection)
RCT_TEXT_PROPERTY(Opacity, _opacity, CGFloat)
RCT_TEXT_PROPERTY(TextShadowOffset, _textShadowOffset, CGSize);
RCT_TEXT_PROPERTY(TextShadowRadius, _textShadowRadius, CGFloat);
RCT_TEXT_PROPERTY(TextShadowColor, _textShadowColor, UIColor *);
- (void)setAllowFontScaling:(BOOL)allowFontScaling
{
_allowFontScaling = allowFontScaling;
for (RCTShadowView *child in [self reactSubviews]) {
if ([child isKindOfClass:[RCTTextShadowView class]]) {
((RCTTextShadowView *)child).allowFontScaling = allowFontScaling;
}
}
[self dirtyText];
}
- (void)setFontSizeMultiplier:(CGFloat)fontSizeMultiplier
{
_fontSizeMultiplier = fontSizeMultiplier;
if (_fontSizeMultiplier == 0) {
RCTLogError(@"fontSizeMultiplier value must be > zero.");
_fontSizeMultiplier = 1.0;
}
for (RCTShadowView *child in [self reactSubviews]) {
if ([child isKindOfClass:[RCTTextShadowView class]]) {
((RCTTextShadowView *)child).fontSizeMultiplier = fontSizeMultiplier;
}
}
[self dirtyText];
}
- (void)setMinimumFontScale:(CGFloat)minimumFontScale
{
if (minimumFontScale >= 0.01) {
_minimumFontScale = minimumFontScale;
}
[self dirtyText];
return (YGSize){
RCTYogaFloatFromCoreGraphicsFloat(size.width),
RCTYogaFloatFromCoreGraphicsFloat(size.height)
};
}
@end

View File

@ -9,11 +9,16 @@
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface RCTTextView : UIView
@property (nonatomic, assign) UIEdgeInsets contentInset;
@property (nonatomic, strong) NSTextStorage *textStorage;
@property (nonatomic, assign) CGRect textFrame;
@property (nonatomic, assign) BOOL selectable;
- (void)setTextStorage:(NSTextStorage *)textStorage
contentFrame:(CGRect)contentFrame
descendantViews:(NSArray<UIView *> *)descendantViews;
@end
NS_ASSUME_NONNULL_END

View File

@ -16,31 +16,21 @@
#import "RCTTextShadowView.h"
static void collectNonTextDescendants(RCTTextView *view, NSMutableArray *nonTextDescendants)
{
for (UIView *child in view.reactSubviews) {
if ([child isKindOfClass:[RCTTextView class]]) {
collectNonTextDescendants((RCTTextView *)child, nonTextDescendants);
} else if (!CGRectEqualToRect(child.frame, CGRectZero)) {
[nonTextDescendants addObject:child];
}
}
}
@implementation RCTTextView
{
NSTextStorage *_textStorage;
CAShapeLayer *_highlightLayer;
UILongPressGestureRecognizer *_longPressGestureRecognizer;
NSArray<UIView *> *_Nullable _descendantViews;
NSTextStorage *_Nullable _textStorage;
CGRect _contentFrame;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])) {
_textStorage = [NSTextStorage new];
if (self = [super initWithFrame:frame]) {
self.isAccessibilityElement = YES;
self.accessibilityTraits |= UIAccessibilityTraitStaticText;
self.opaque = NO;
self.contentMode = UIViewContentModeRedraw;
}
@ -51,7 +41,7 @@ static void collectNonTextDescendants(RCTTextView *view, NSMutableArray *nonText
{
NSString *superDescription = super.description;
NSRange semicolonRange = [superDescription rangeOfString:@";"];
NSString *replacement = [NSString stringWithFormat:@"; reactTag: %@; text: %@", self.reactTag, self.textStorage.string];
NSString *replacement = [NSString stringWithFormat:@"; reactTag: %@; text: %@", self.reactTag, _textStorage.string];
return [superDescription stringByReplacingCharactersInRange:semicolonRange withString:replacement];
}
@ -86,54 +76,65 @@ static void collectNonTextDescendants(RCTTextView *view, NSMutableArray *nonText
}
- (void)setTextStorage:(NSTextStorage *)textStorage
contentFrame:(CGRect)contentFrame
descendantViews:(NSArray<UIView *> *)descendantViews
{
if (_textStorage != textStorage) {
_textStorage = textStorage;
_textStorage = textStorage;
_contentFrame = contentFrame;
// Update subviews
NSMutableArray *nonTextDescendants = [NSMutableArray new];
collectNonTextDescendants(self, nonTextDescendants);
NSArray *subviews = self.subviews;
if (![subviews isEqualToArray:nonTextDescendants]) {
for (UIView *child in subviews) {
if (![nonTextDescendants containsObject:child]) {
[child removeFromSuperview];
}
}
for (UIView *child in nonTextDescendants) {
[self addSubview:child];
}
}
[self setNeedsDisplay];
// FIXME: Optimize this.
for (UIView *view in _descendantViews) {
[view removeFromSuperview];
}
_descendantViews = descendantViews;
for (UIView *view in descendantViews) {
[self addSubview:view];
}
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
NSLayoutManager *layoutManager = [_textStorage.layoutManagers firstObject];
NSTextContainer *textContainer = [layoutManager.textContainers firstObject];
if (!_textStorage) {
return;
}
NSLayoutManager *layoutManager = _textStorage.layoutManagers.firstObject;
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
CGRect textFrame = self.textFrame;
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:textFrame.origin];
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:textFrame.origin];
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:_contentFrame.origin];
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:_contentFrame.origin];
__block UIBezierPath *highlightPath = nil;
NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL];
[layoutManager.textStorage enumerateAttribute:RCTIsHighlightedAttributeName inRange:characterRange options:0 usingBlock:^(NSNumber *value, NSRange range, BOOL *_) {
if (!value.boolValue) {
return;
}
[layoutManager enumerateEnclosingRectsForGlyphRange:range withinSelectedGlyphRange:range inTextContainer:textContainer usingBlock:^(CGRect enclosingRect, __unused BOOL *__) {
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(enclosingRect, -2, -2) cornerRadius:2];
if (highlightPath) {
[highlightPath appendPath:path];
} else {
highlightPath = path;
NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange
actualGlyphRange:NULL];
[_textStorage enumerateAttribute:RCTTextAttributesIsHighlightedAttributeName
inRange:characterRange
options:0
usingBlock:
^(NSNumber *value, NSRange range, __unused BOOL *stop) {
if (!value.boolValue) {
return;
}
}];
[layoutManager enumerateEnclosingRectsForGlyphRange:range
withinSelectedGlyphRange:range
inTextContainer:textContainer
usingBlock:
^(CGRect enclosingRect, __unused BOOL *anotherStop) {
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(enclosingRect, -2, -2) cornerRadius:2];
if (highlightPath) {
[highlightPath appendPath:path];
} else {
highlightPath = path;
}
}
];
}];
if (highlightPath) {
@ -142,7 +143,7 @@ static void collectNonTextDescendants(RCTTextView *view, NSMutableArray *nonText
_highlightLayer.fillColor = [UIColor colorWithWhite:0 alpha:0.25].CGColor;
[self.layer addSublayer:_highlightLayer];
}
_highlightLayer.position = (CGPoint){_contentInset.left, _contentInset.top};
_highlightLayer.position = _contentFrame.origin;
_highlightLayer.path = highlightPath.CGPath;
} else {
[_highlightLayer removeFromSuperlayer];
@ -150,6 +151,7 @@ static void collectNonTextDescendants(RCTTextView *view, NSMutableArray *nonText
}
}
- (NSNumber *)reactTagAtPoint:(CGPoint)point
{
NSNumber *reactTag = self.reactTag;
@ -164,8 +166,9 @@ static void collectNonTextDescendants(RCTTextView *view, NSMutableArray *nonText
// If the point is not before (fraction == 0.0) the first character and not
// after (fraction == 1.0) the last character, then the attribute is valid.
if (_textStorage.length > 0 && (fraction > 0 || characterIndex > 0) && (fraction < 1 || characterIndex < _textStorage.length - 1)) {
reactTag = [_textStorage attribute:RCTReactTagAttributeName atIndex:characterIndex effectiveRange:NULL];
reactTag = [_textStorage attribute:RCTTextAttributesTagAttributeName atIndex:characterIndex effectiveRange:NULL];
}
return reactTag;
}
@ -179,12 +182,11 @@ static void collectNonTextDescendants(RCTTextView *view, NSMutableArray *nonText
[_highlightLayer removeFromSuperlayer];
_highlightLayer = nil;
}
} else if (_textStorage.length) {
} else if (_textStorage) {
[self setNeedsDisplay];
}
}
#pragma mark - Accessibility
- (NSString *)accessibilityLabel
@ -245,19 +247,19 @@ static void collectNonTextDescendants(RCTTextView *view, NSMutableArray *nonText
- (void)copy:(id)sender
{
#if !TARGET_OS_TV
NSAttributedString *attributedString = _textStorage;
NSAttributedString *attributedText = _textStorage;
NSMutableDictionary *item = [NSMutableDictionary new];
NSData *rtf = [attributedString dataFromRange:NSMakeRange(0, attributedString.length)
documentAttributes:@{NSDocumentTypeDocumentAttribute: NSRTFDTextDocumentType}
error:nil];
NSData *rtf = [attributedText dataFromRange:NSMakeRange(0, attributedText.length)
documentAttributes:@{NSDocumentTypeDocumentAttribute: NSRTFDTextDocumentType}
error:nil];
if (rtf) {
[item setObject:rtf forKey:(id)kUTTypeFlatRTFD];
}
[item setObject:attributedString.string forKey:(id)kUTTypeUTF8PlainText];
[item setObject:attributedText.string forKey:(id)kUTTypeUTF8PlainText];
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
pasteboard.items = @[item];

View File

@ -9,6 +9,8 @@
#import <React/RCTViewManager.h>
@interface RCTTextViewManager : RCTViewManager
#import "RCTBaseTextViewManager.h"
@interface RCTTextViewManager : RCTBaseTextViewManager
@end

View File

@ -10,41 +10,51 @@
#import "RCTTextViewManager.h"
#import <React/RCTAccessibilityManager.h>
#import <React/RCTAssert.h>
#import <React/RCTConvert.h>
#import <React/RCTLog.h>
#import <React/RCTShadowView+Layout.h>
#import <React/UIView+React.h>
#import <yoga/Yoga.h>
#import <React/RCTShadowView.h>
#import <React/RCTUIManager.h>
#import <React/RCTUIManagerObserverCoordinator.h>
#import "RCTRawTextShadowView.h"
#import "RCTTextShadowView.h"
#import "RCTTextView.h"
#import "RCTMultilineTextInputView.h"
static void collectDirtyNonTextDescendants(RCTTextShadowView *shadowView, NSMutableArray *nonTextDescendants) {
for (RCTShadowView *child in shadowView.reactSubviews) {
if ([child isKindOfClass:[RCTTextShadowView class]]) {
collectDirtyNonTextDescendants((RCTTextShadowView *)child, nonTextDescendants);
} else if ([child isKindOfClass:[RCTRawTextShadowView class]]) {
// no-op
} else if ([child isTextDirty]) {
[nonTextDescendants addObject:child];
}
}
}
@interface RCTTextShadowView (Private)
- (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width widthMode:(YGMeasureMode)widthMode;
@interface RCTTextViewManager () <RCTUIManagerObserver>
@end
@implementation RCTTextViewManager
{
NSHashTable<RCTTextShadowView *> *_shadowViews;
CGFloat _fontSizeMultiplier;
}
RCT_EXPORT_MODULE(RCTText)
RCT_REMAP_SHADOW_PROPERTY(numberOfLines, maximumNumberOfLines, NSInteger)
RCT_REMAP_SHADOW_PROPERTY(ellipsizeMode, lineBreakMode, NSLineBreakMode)
RCT_REMAP_SHADOW_PROPERTY(adjustsFontSizeToFit, adjustsFontSizeToFit, BOOL)
RCT_REMAP_SHADOW_PROPERTY(minimumFontScale, minimumFontScale, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(selectable, BOOL)
- (void)setBridge:(RCTBridge *)bridge
{
[super setBridge:bridge];
_shadowViews = [NSHashTable weakObjectsHashTable];
[bridge.uiManager.observerCoordinator addObserver:self];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleDidUpdateMultiplierNotification)
name:RCTAccessibilityManagerDidUpdateMultiplierNotification
object:bridge.accessibilityManager];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (UIView *)view
{
return [RCTTextView new];
@ -52,86 +62,33 @@ RCT_EXPORT_MODULE(RCTText)
- (RCTShadowView *)shadowView
{
return [RCTTextShadowView new];
RCTTextShadowView *shadowView = [[RCTTextShadowView alloc] initWithBridge:self.bridge];
shadowView.textAttributes.fontSizeMultiplier = self.bridge.accessibilityManager.multiplier;
[_shadowViews addObject:shadowView];
return shadowView;
}
#pragma mark - Shadow properties
#pragma mark - RCTUIManagerObserver
RCT_EXPORT_SHADOW_PROPERTY(color, UIColor)
RCT_EXPORT_SHADOW_PROPERTY(backgroundColor, UIColor)
RCT_EXPORT_SHADOW_PROPERTY(fontFamily, NSString)
RCT_EXPORT_SHADOW_PROPERTY(fontSize, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(fontWeight, NSString)
RCT_EXPORT_SHADOW_PROPERTY(fontStyle, NSString)
RCT_EXPORT_SHADOW_PROPERTY(fontVariant, NSArray)
RCT_EXPORT_SHADOW_PROPERTY(isHighlighted, BOOL)
RCT_EXPORT_SHADOW_PROPERTY(letterSpacing, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(lineHeight, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(numberOfLines, NSUInteger)
RCT_EXPORT_SHADOW_PROPERTY(ellipsizeMode, NSLineBreakMode)
RCT_EXPORT_SHADOW_PROPERTY(textAlign, NSTextAlignment)
RCT_EXPORT_SHADOW_PROPERTY(textDecorationStyle, NSUnderlineStyle)
RCT_EXPORT_SHADOW_PROPERTY(textDecorationColor, UIColor)
RCT_EXPORT_SHADOW_PROPERTY(textDecorationLine, RCTTextDecorationLineType)
RCT_EXPORT_SHADOW_PROPERTY(writingDirection, NSWritingDirection)
RCT_EXPORT_SHADOW_PROPERTY(allowFontScaling, BOOL)
RCT_EXPORT_SHADOW_PROPERTY(opacity, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(textShadowOffset, CGSize)
RCT_EXPORT_SHADOW_PROPERTY(textShadowRadius, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(textShadowColor, UIColor)
RCT_EXPORT_SHADOW_PROPERTY(adjustsFontSizeToFit, BOOL)
RCT_EXPORT_SHADOW_PROPERTY(minimumFontScale, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(selectable, BOOL)
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(NSDictionary<NSNumber *, RCTShadowView *> *)shadowViewRegistry
- (void)uiManagerWillPerformMounting:(__unused RCTUIManager *)uiManager
{
for (RCTShadowView *rootView in shadowViewRegistry.allValues) {
if (![rootView isReactRootView]) {
// This isn't a root view
continue;
}
for (RCTTextShadowView *shadowView in _shadowViews) {
[shadowView uiManagerWillPerformMounting];
}
}
if (![rootView isTextDirty]) {
// No text processing to be done
continue;
}
#pragma mark - Font Size Multiplier
NSMutableArray<RCTShadowView *> *queue = [NSMutableArray arrayWithObject:rootView];
for (NSInteger i = 0; i < queue.count; i++) {
RCTShadowView *shadowView = queue[i];
RCTAssert([shadowView isTextDirty], @"Don't process any nodes that don't have dirty text");
- (void)handleDidUpdateMultiplierNotification
{
CGFloat fontSizeMultiplier = self.bridge.accessibilityManager.multiplier;
if ([shadowView isKindOfClass:[RCTTextShadowView class]]) {
((RCTTextShadowView *)shadowView).fontSizeMultiplier = self.bridge.accessibilityManager.multiplier;
[(RCTTextShadowView *)shadowView recomputeText];
collectDirtyNonTextDescendants((RCTTextShadowView *)shadowView, queue);
} else if ([shadowView isKindOfClass:[RCTRawTextShadowView class]]) {
RCTLogError(@"Raw text cannot be used outside of a <Text> tag. Not rendering string: '%@'",
[(RCTRawTextShadowView *)shadowView text]);
} else {
for (RCTShadowView *child in [shadowView reactSubviews]) {
if ([child isTextDirty]) {
[queue addObject:child];
}
}
}
[shadowView setTextComputed];
}
for (RCTTextShadowView *shadowView in _shadowViews) {
shadowView.textAttributes.fontSizeMultiplier = fontSizeMultiplier;
[shadowView dirtyLayout];
}
return nil;
}
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTTextShadowView *)shadowView
{
NSNumber *reactTag = shadowView.reactTag;
UIEdgeInsets padding = shadowView.paddingAsInsets;
return ^(RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTTextView *> *viewRegistry) {
RCTTextView *text = viewRegistry[reactTag];
text.contentInset = padding;
};
[self.bridge.uiManager setNeedsLayout];
}
@end

View File

@ -1,19 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTMultilineTextInputShadowView.h"
@implementation RCTMultilineTextInputShadowView
- (BOOL)isYogaLeafNode
{
return YES;
}
@end

View File

@ -7,30 +7,12 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
#import <React/RCTView.h>
#import <React/UIView+React.h>
#import "RCTBaseTextInputView.h"
@class RCTBridge;
NS_ASSUME_NONNULL_BEGIN
@interface RCTMultilineTextInputView : RCTBaseTextInputView
@property (nonatomic, assign) UITextAutocorrectionType autocorrectionType;
@property (nonatomic, assign) UITextSpellCheckingType spellCheckingType;
@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
@property (nonatomic, copy) NSString *text;
@property (nonatomic, strong) UIColor *placeholderTextColor;
@property (nonatomic, copy) NSString *placeholder;
@property (nonatomic, strong) UIFont *font;
@property (nonatomic, strong) NSNumber *maxLength;
@property (nonatomic, copy) RCTDirectEventBlock onChange;
@property (nonatomic, copy) RCTDirectEventBlock onTextInput;
@property (nonatomic, copy) RCTDirectEventBlock onScroll;
- (void)performTextUpdate;
@end
NS_ASSUME_NONNULL_END

View File

@ -9,382 +9,60 @@
#import "RCTMultilineTextInputView.h"
#import <React/RCTConvert.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTFont.h>
#import <React/RCTUIManager.h>
#import <React/RCTUtils.h>
#import <React/UIView+React.h>
#import "RCTTextShadowView.h"
#import "RCTTextView.h"
#import "RCTTextSelection.h"
#import "RCTUITextView.h"
@interface RCTMultilineTextInputView () <RCTBackedTextInputDelegate>
@end
@implementation RCTMultilineTextInputView
{
RCTUITextView *_backedTextInput;
RCTTextView *_richTextView;
NSAttributedString *_pendingAttributedText;
NSString *_predictedText;
BOOL _blockTextShouldChange;
BOOL _nativeUpdatesInFlight;
RCTUITextView *_backedTextInputView;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
RCTAssertParam(bridge);
if (self = [super initWithBridge:bridge]) {
// `blurOnSubmit` defaults to `false` for <TextInput multiline={true}> by design.
_blurOnSubmit = NO;
self.blurOnSubmit = NO;
_backedTextInput = [[RCTUITextView alloc] initWithFrame:self.bounds];
_backedTextInput.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_backedTextInput.backgroundColor = [UIColor clearColor];
_backedTextInput.textColor = [UIColor blackColor];
_backedTextInputView = [[RCTUITextView alloc] initWithFrame:self.bounds];
_backedTextInputView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_backedTextInputView.backgroundColor = [UIColor clearColor];
_backedTextInputView.textColor = [UIColor blackColor];
// This line actually removes 5pt (default value) left and right padding in UITextView.
_backedTextInput.textContainer.lineFragmentPadding = 0;
_backedTextInputView.textContainer.lineFragmentPadding = 0;
#if !TARGET_OS_TV
_backedTextInput.scrollsToTop = NO;
_backedTextInputView.scrollsToTop = NO;
#endif
_backedTextInput.scrollEnabled = YES;
_backedTextInput.textInputDelegate = self;
_backedTextInput.font = self.fontAttributes.font;
_backedTextInputView.scrollEnabled = YES;
_backedTextInputView.textInputDelegate = self;
[self addSubview:_backedTextInput];
[self addSubview:_backedTextInputView];
}
return self;
}
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)coder)
- (id<RCTBackedTextInputViewProtocol>)backedTextInputView
{
return _backedTextInput;
}
#pragma mark - RCTComponent
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)index
{
[super insertReactSubview:subview atIndex:index];
if ([subview isKindOfClass:[RCTTextView class]]) {
if (_richTextView) {
RCTLogError(@"Tried to insert a second <Text> into <TextInput> - there can only be one.");
}
_richTextView = (RCTTextView *)subview;
// If this <TextInput> is in rich text editing mode, and the child <Text> node providing rich text
// styling has a backgroundColor, then the attributedText produced by the child <Text> node will have an
// NSBackgroundColor attribute. We need to forward this attribute to the text view manually because the text view
// always has a clear background color in `initWithBridge:`.
//
// TODO: This should be removed when the related hack in -performPendingTextUpdate is removed.
if (subview.backgroundColor) {
NSMutableDictionary<NSString *, id> *attrs = [_backedTextInput.typingAttributes mutableCopy];
attrs[NSBackgroundColorAttributeName] = subview.backgroundColor;
_backedTextInput.typingAttributes = attrs;
}
[self performTextUpdate];
}
}
- (void)removeReactSubview:(UIView *)subview
{
[super removeReactSubview:subview];
if (_richTextView == subview) {
_richTextView = nil;
[self performTextUpdate];
}
}
- (void)didUpdateReactSubviews
{
// Do nothing, as we don't allow non-text subviews.
}
#pragma mark - Routine
- (void)setMostRecentEventCount:(NSInteger)mostRecentEventCount
{
_mostRecentEventCount = mostRecentEventCount;
// Props are set after uiBlockToAmendWithShadowViewRegistry, which means that
// at the time performTextUpdate is called, _mostRecentEventCount will be
// behind _eventCount, with the result that performPendingTextUpdate will do
// nothing. For that reason we call it again here after mostRecentEventCount
// has been set.
[self performPendingTextUpdate];
}
- (void)performTextUpdate
{
if (_richTextView) {
_pendingAttributedText = _richTextView.textStorage;
[self performPendingTextUpdate];
} else if (!self.text) {
_backedTextInput.attributedText = nil;
}
}
static NSAttributedString *removeReactTagFromString(NSAttributedString *string)
{
if (string.length == 0) {
return string;
} else {
NSMutableAttributedString *mutableString = [[NSMutableAttributedString alloc] initWithAttributedString:string];
[mutableString removeAttribute:RCTReactTagAttributeName range:NSMakeRange(0, mutableString.length)];
return mutableString;
}
}
- (void)performPendingTextUpdate
{
if (!_pendingAttributedText || _mostRecentEventCount < _nativeEventCount || _nativeUpdatesInFlight) {
return;
}
// The underlying <Text> node that produces _pendingAttributedText has a react tag attribute on it that causes the
// -isEqualToAttributedString: comparison below to spuriously fail. We don't want that comparison to fail unless it
// needs to because when the comparison fails, we end up setting attributedText on the text view, which clears
// autocomplete state for CKJ text input.
//
// TODO: Kill this after we finish passing all style/attribute info into JS.
_pendingAttributedText = removeReactTagFromString(_pendingAttributedText);
if ([_backedTextInput.attributedText isEqualToAttributedString:_pendingAttributedText]) {
_pendingAttributedText = nil; // Don't try again.
return;
}
// When we update the attributed text, there might be pending autocorrections
// that will get accepted by default. In order for this to not garble our text,
// we temporarily block all textShouldChange events so they are not applied.
_blockTextShouldChange = YES;
UITextRange *selection = _backedTextInput.selectedTextRange;
NSInteger oldTextLength = _backedTextInput.attributedText.length;
_backedTextInput.attributedText = _pendingAttributedText;
_predictedText = _pendingAttributedText.string;
_pendingAttributedText = nil;
if (selection.empty) {
// maintain cursor position relative to the end of the old text
NSInteger start = [_backedTextInput offsetFromPosition:_backedTextInput.beginningOfDocument toPosition:selection.start];
NSInteger offsetFromEnd = oldTextLength - start;
NSInteger newOffset = _backedTextInput.attributedText.length - offsetFromEnd;
UITextPosition *position = [_backedTextInput positionFromPosition:_backedTextInput.beginningOfDocument offset:newOffset];
[_backedTextInput setSelectedTextRange:[_backedTextInput textRangeFromPosition:position toPosition:position]
notifyDelegate:YES];
}
[_backedTextInput layoutIfNeeded];
[self invalidateContentSize];
_blockTextShouldChange = NO;
}
#pragma mark - Properties
- (UIFont *)font
{
return _backedTextInput.font;
}
- (void)setFont:(UIFont *)font
{
_backedTextInput.font = font;
[self setNeedsLayout];
}
- (NSString *)text
{
return _backedTextInput.text;
}
- (void)setText:(NSString *)text
{
NSInteger eventLag = _nativeEventCount - _mostRecentEventCount;
if (eventLag == 0 && ![text isEqualToString:_backedTextInput.text]) {
UITextRange *selection = _backedTextInput.selectedTextRange;
NSInteger oldTextLength = _backedTextInput.text.length;
_predictedText = text;
_backedTextInput.text = text;
if (selection.empty) {
// maintain cursor position relative to the end of the old text
NSInteger start = [_backedTextInput offsetFromPosition:_backedTextInput.beginningOfDocument toPosition:selection.start];
NSInteger offsetFromEnd = oldTextLength - start;
NSInteger newOffset = text.length - offsetFromEnd;
UITextPosition *position = [_backedTextInput positionFromPosition:_backedTextInput.beginningOfDocument offset:newOffset];
[_backedTextInput setSelectedTextRange:[_backedTextInput textRangeFromPosition:position toPosition:position]
notifyDelegate:YES];
}
[self invalidateContentSize];
} else if (eventLag > RCTTextUpdateLagWarningThreshold) {
RCTLogWarn(@"Native TextInput(%@) is %lld events ahead of JS - try to make your JS faster.", self.text, (long long)eventLag);
}
}
#pragma mark - RCTBackedTextInputDelegate
- (BOOL)textInputShouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
if (!_backedTextInput.textWasPasted) {
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeKeyPress
reactTag:self.reactTag
text:nil
key:text
eventCount:_nativeEventCount];
}
// So we need to track that there is a native update in flight just in case JS manages to come back around and update
// things /before/ UITextView can update itself asynchronously. If there is a native update in flight, we defer the
// JS update when it comes in and apply the deferred update once textViewDidChange fires with the native update applied.
if (_blockTextShouldChange) {
return NO;
}
if (_maxLength) {
NSUInteger allowedLength = _maxLength.integerValue - _backedTextInput.text.length + range.length;
if (text.length > allowedLength) {
// If we typed/pasted more than one character, limit the text inputted
if (text.length > 1) {
// Truncate the input string so the result is exactly maxLength
NSString *limitedString = [text substringToIndex:allowedLength];
NSMutableString *newString = _backedTextInput.text.mutableCopy;
[newString replaceCharactersInRange:range withString:limitedString];
_backedTextInput.text = newString;
_predictedText = newString;
// Collapse selection at end of insert to match normal paste behavior
UITextPosition *insertEnd = [_backedTextInput positionFromPosition:_backedTextInput.beginningOfDocument
offset:(range.location + allowedLength)];
[_backedTextInput setSelectedTextRange:[_backedTextInput textRangeFromPosition:insertEnd toPosition:insertEnd]
notifyDelegate:YES];
[self textInputDidChange];
}
return NO;
}
}
_nativeUpdatesInFlight = YES;
if (range.location + range.length > _predictedText.length) {
// _predictedText got out of sync in a bad way, so let's just force sync it. Haven't been able to repro this, but
// it's causing a real crash here: #6523822
_predictedText = _backedTextInput.text;
}
NSString *previousText = [_predictedText substringWithRange:range];
if (_predictedText) {
_predictedText = [_predictedText stringByReplacingCharactersInRange:range withString:text];
} else {
_predictedText = text;
}
if (_onTextInput) {
_onTextInput(@{
@"text": text,
@"previousText": previousText ?: @"",
@"range": @{
@"start": @(range.location),
@"end": @(range.location + range.length)
},
@"eventCount": @(_nativeEventCount),
});
}
return YES;
}
static BOOL findMismatch(NSString *first, NSString *second, NSRange *firstRange, NSRange *secondRange)
{
NSInteger firstMismatch = -1;
for (NSUInteger ii = 0; ii < MAX(first.length, second.length); ii++) {
if (ii >= first.length || ii >= second.length || [first characterAtIndex:ii] != [second characterAtIndex:ii]) {
firstMismatch = ii;
break;
}
}
if (firstMismatch == -1) {
return NO;
}
NSUInteger ii = second.length;
NSUInteger lastMismatch = first.length;
while (ii > firstMismatch && lastMismatch > firstMismatch) {
if ([first characterAtIndex:(lastMismatch - 1)] != [second characterAtIndex:(ii - 1)]) {
break;
}
ii--;
lastMismatch--;
}
*firstRange = NSMakeRange(firstMismatch, lastMismatch - firstMismatch);
*secondRange = NSMakeRange(firstMismatch, ii - firstMismatch);
return YES;
}
- (void)textInputDidChange
{
[self invalidateContentSize];
// Detect when _backedTextInput updates happened that didn't invoke `shouldChangeTextInRange`
// (e.g. typing simplified chinese in pinyin will insert and remove spaces without
// calling shouldChangeTextInRange). This will cause JS to get out of sync so we
// update the mismatched range.
NSRange currentRange;
NSRange predictionRange;
if (findMismatch(_backedTextInput.text, _predictedText, &currentRange, &predictionRange)) {
NSString *replacement = [_backedTextInput.text substringWithRange:currentRange];
[self textInputShouldChangeTextInRange:predictionRange replacementText:replacement];
// JS will assume the selection changed based on the location of our shouldChangeTextInRange, so reset it.
[self textInputDidChangeSelection];
_predictedText = _backedTextInput.text;
}
_nativeUpdatesInFlight = NO;
_nativeEventCount++;
if (!self.reactTag || !_onChange) {
return;
}
_onChange(@{
@"text": self.text,
@"target": self.reactTag,
@"eventCount": @(_nativeEventCount),
});
return _backedTextInputView;
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (_onScroll) {
RCTDirectEventBlock onScroll = self.onScroll;
if (onScroll) {
CGPoint contentOffset = scrollView.contentOffset;
CGSize contentSize = scrollView.contentSize;
CGSize size = scrollView.bounds.size;
UIEdgeInsets contentInset = scrollView.contentInset;
_onScroll(@{
onScroll(@{
@"contentOffset": @{
@"x": @(contentOffset.x),
@"y": @(contentOffset.y)

View File

@ -9,6 +9,10 @@
#import "RCTBaseTextInputViewManager.h"
NS_ASSUME_NONNULL_BEGIN
@interface RCTMultilineTextInputViewManager : RCTBaseTextInputViewManager
@end
NS_ASSUME_NONNULL_END

View File

@ -9,25 +9,12 @@
#import "RCTMultilineTextInputViewManager.h"
#import <React/RCTBridge.h>
#import <React/RCTConvert.h>
#import <React/RCTFont.h>
#import <React/RCTShadowView+Layout.h>
#import <React/RCTShadowView.h>
#import "RCTConvert+Text.h"
#import "RCTMultilineTextInputShadowView.h"
#import "RCTMultilineTextInputView.h"
@implementation RCTMultilineTextInputViewManager
RCT_EXPORT_MODULE()
- (RCTShadowView *)shadowView
{
return [RCTMultilineTextInputShadowView new];
}
- (UIView *)view
{
return [[RCTMultilineTextInputView alloc] initWithBridge:self.bridge];
@ -35,12 +22,6 @@ RCT_EXPORT_MODULE()
#pragma mark - Multiline <TextInput> (aka TextView) specific properties
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onContentSizeChange, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onTextInput, RCTDirectEventBlock)
#if !TARGET_OS_TV
RCT_REMAP_VIEW_PROPERTY(dataDetectorTypes, backedTextInputView.dataDetectorTypes, UIDataDetectorTypes)
#endif

View File

@ -21,7 +21,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface RCTUITextView : UITextView <RCTBackedTextInputViewProtocol>
- (instancetype)initWithFrame:(CGRect)frame textContainer:(nullable NSTextContainer *)textContainer NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder *)decoder NS_UNAVAILABLE;
@property (nonatomic, weak) id<RCTBackedTextInputDelegate> textInputDelegate;

View File

@ -66,7 +66,7 @@ static UIColor *defaultPlaceholderColor()
[accessibilityLabel appendString:superAccessibilityLabel];
}
if (self.placeholder.length > 0 && self.text.length == 0) {
if (self.placeholder.length > 0 && self.attributedText.string.length == 0) {
if (accessibilityLabel.length > 0) {
[accessibilityLabel appendString:@" "];
}
@ -193,7 +193,7 @@ static UIColor *defaultPlaceholderColor()
- (CGSize)intrinsicContentSize
{
// Returning size DOES contain `textContainerInset` (aka `padding`).
return [self sizeThatFits:CGSizeMake(self.preferredMaxLayoutWidth, INFINITY)];
return [self sizeThatFits:CGSizeMake(self.preferredMaxLayoutWidth, CGFLOAT_MAX)];
}
- (CGSize)sizeThatFits:(CGSize)size
@ -234,7 +234,7 @@ static UIColor *defaultPlaceholderColor()
- (void)invalidatePlaceholderVisibility
{
BOOL isVisible = _placeholder.length != 0 && self.text.length == 0;
BOOL isVisible = _placeholder.length != 0 && self.attributedText.length == 0;
_placeholderView.hidden = !isVisible;
}

View File

@ -11,6 +11,8 @@
@protocol RCTBackedTextInputViewProtocol;
NS_ASSUME_NONNULL_BEGIN
@protocol RCTBackedTextInputDelegate <NSObject>
- (BOOL)textInputShouldBeginEditing; // Return `NO` to disallow editing.
@ -28,3 +30,5 @@
- (void)textInputDidChangeSelection;
@end
NS_ASSUME_NONNULL_END

View File

@ -12,11 +12,13 @@
#import "RCTBackedTextInputViewProtocol.h"
#import "RCTBackedTextInputDelegate.h"
NS_ASSUME_NONNULL_BEGIN
#pragma mark - RCTBackedTextFieldDelegateAdapter (for UITextField)
@interface RCTBackedTextFieldDelegateAdapter : NSObject
- (instancetype)initWithTextField:(UITextField<RCTBackedTextInputViewProtocol> *)backedTextInput;
- (instancetype)initWithTextField:(UITextField<RCTBackedTextInputViewProtocol> *)backedTextInputView;
- (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)textRange;
- (void)selectedTextRangeWasSet;
@ -27,8 +29,10 @@
@interface RCTBackedTextViewDelegateAdapter : NSObject
- (instancetype)initWithTextView:(UITextView<RCTBackedTextInputViewProtocol> *)backedTextInput;
- (instancetype)initWithTextView:(UITextView<RCTBackedTextInputViewProtocol> *)backedTextInputView;
- (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)textRange;
@end
NS_ASSUME_NONNULL_END

View File

@ -17,19 +17,19 @@ static void *TextFieldSelectionObservingContext = &TextFieldSelectionObservingCo
@end
@implementation RCTBackedTextFieldDelegateAdapter {
__weak UITextField<RCTBackedTextInputViewProtocol> *_backedTextInput;
__weak UITextField<RCTBackedTextInputViewProtocol> *_backedTextInputView;
BOOL _textDidChangeIsComing;
UITextRange *_previousSelectedTextRange;
}
- (instancetype)initWithTextField:(UITextField<RCTBackedTextInputViewProtocol> *)backedTextInput
- (instancetype)initWithTextField:(UITextField<RCTBackedTextInputViewProtocol> *)backedTextInputView
{
if (self = [super init]) {
_backedTextInput = backedTextInput;
backedTextInput.delegate = self;
_backedTextInputView = backedTextInputView;
backedTextInputView.delegate = self;
[_backedTextInput addTarget:self action:@selector(textFieldDidChange) forControlEvents:UIControlEventEditingChanged];
[_backedTextInput addTarget:self action:@selector(textFieldDidEndEditingOnExit) forControlEvents:UIControlEventEditingDidEndOnExit];
[_backedTextInputView addTarget:self action:@selector(textFieldDidChange) forControlEvents:UIControlEventEditingChanged];
[_backedTextInputView addTarget:self action:@selector(textFieldDidEndEditingOnExit) forControlEvents:UIControlEventEditingDidEndOnExit];
}
return self;
@ -37,25 +37,25 @@ static void *TextFieldSelectionObservingContext = &TextFieldSelectionObservingCo
- (void)dealloc
{
[_backedTextInput removeTarget:self action:nil forControlEvents:UIControlEventEditingChanged];
[_backedTextInput removeTarget:self action:nil forControlEvents:UIControlEventEditingDidEndOnExit];
[_backedTextInputView removeTarget:self action:nil forControlEvents:UIControlEventEditingChanged];
[_backedTextInputView removeTarget:self action:nil forControlEvents:UIControlEventEditingDidEndOnExit];
}
#pragma mark - UITextFieldDelegate
- (BOOL)textFieldShouldBeginEditing:(__unused UITextField *)textField
{
return [_backedTextInput.textInputDelegate textInputShouldBeginEditing];
return [_backedTextInputView.textInputDelegate textInputShouldBeginEditing];
}
- (void)textFieldDidBeginEditing:(__unused UITextField *)textField
{
[_backedTextInput.textInputDelegate textInputDidBeginEditing];
[_backedTextInputView.textInputDelegate textInputDidBeginEditing];
}
- (BOOL)textFieldShouldEndEditing:(__unused UITextField *)textField
{
return [_backedTextInput.textInputDelegate textInputShouldEndEditing];
return [_backedTextInputView.textInputDelegate textInputShouldEndEditing];
}
- (void)textFieldDidEndEditing:(__unused UITextField *)textField
@ -64,15 +64,15 @@ static void *TextFieldSelectionObservingContext = &TextFieldSelectionObservingCo
// iOS does't call `textViewDidChange:` delegate method if the change was happened because of autocorrection
// which was triggered by losing focus. So, we call it manually.
_textDidChangeIsComing = NO;
[_backedTextInput.textInputDelegate textInputDidChange];
[_backedTextInputView.textInputDelegate textInputDidChange];
}
[_backedTextInput.textInputDelegate textInputDidEndEditing];
[_backedTextInputView.textInputDelegate textInputDidEndEditing];
}
- (BOOL)textField:(__unused UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
BOOL result = [_backedTextInput.textInputDelegate textInputShouldChangeTextInRange:range replacementText:string];
BOOL result = [_backedTextInputView.textInputDelegate textInputShouldChangeTextInRange:range replacementText:string];
if (result) {
_textDidChangeIsComing = YES;
}
@ -81,7 +81,7 @@ static void *TextFieldSelectionObservingContext = &TextFieldSelectionObservingCo
- (BOOL)textFieldShouldReturn:(__unused UITextField *)textField
{
return [_backedTextInput.textInputDelegate textInputShouldReturn];
return [_backedTextInputView.textInputDelegate textInputShouldReturn];
}
#pragma mark - UIControlEventEditing* Family Events
@ -89,7 +89,7 @@ static void *TextFieldSelectionObservingContext = &TextFieldSelectionObservingCo
- (void)textFieldDidChange
{
_textDidChangeIsComing = NO;
[_backedTextInput.textInputDelegate textInputDidChange];
[_backedTextInputView.textInputDelegate textInputDidChange];
// `selectedTextRangeWasSet` isn't triggered during typing.
[self textFieldProbablyDidChangeSelection];
@ -97,7 +97,7 @@ static void *TextFieldSelectionObservingContext = &TextFieldSelectionObservingCo
- (void)textFieldDidEndEditingOnExit
{
[_backedTextInput.textInputDelegate textInputDidReturn];
[_backedTextInputView.textInputDelegate textInputDidReturn];
}
#pragma mark - UIKeyboardInput (private UIKit protocol)
@ -106,7 +106,7 @@ static void *TextFieldSelectionObservingContext = &TextFieldSelectionObservingCo
// even when there is no more text in the `UITextField`.
- (BOOL)keyboardInputShouldDelete:(__unused UITextField *)textField
{
[_backedTextInput.textInputDelegate textInputShouldChangeTextInRange:NSMakeRange(0, 0) replacementText:@""];
[_backedTextInputView.textInputDelegate textInputShouldChangeTextInRange:NSMakeRange(0, 0) replacementText:@""];
return YES;
}
@ -126,12 +126,12 @@ static void *TextFieldSelectionObservingContext = &TextFieldSelectionObservingCo
- (void)textFieldProbablyDidChangeSelection
{
if ([_backedTextInput.selectedTextRange isEqual:_previousSelectedTextRange]) {
if ([_backedTextInputView.selectedTextRange isEqual:_previousSelectedTextRange]) {
return;
}
_previousSelectedTextRange = _backedTextInput.selectedTextRange;
[_backedTextInput.textInputDelegate textInputDidChangeSelection];
_previousSelectedTextRange = _backedTextInputView.selectedTextRange;
[_backedTextInputView.textInputDelegate textInputDidChangeSelection];
}
@end
@ -142,16 +142,16 @@ static void *TextFieldSelectionObservingContext = &TextFieldSelectionObservingCo
@end
@implementation RCTBackedTextViewDelegateAdapter {
__weak UITextView<RCTBackedTextInputViewProtocol> *_backedTextInput;
__weak UITextView<RCTBackedTextInputViewProtocol> *_backedTextInputView;
BOOL _textDidChangeIsComing;
UITextRange *_previousSelectedTextRange;
}
- (instancetype)initWithTextView:(UITextView<RCTBackedTextInputViewProtocol> *)backedTextInput
- (instancetype)initWithTextView:(UITextView<RCTBackedTextInputViewProtocol> *)backedTextInputView
{
if (self = [super init]) {
_backedTextInput = backedTextInput;
backedTextInput.delegate = self;
_backedTextInputView = backedTextInputView;
backedTextInputView.delegate = self;
}
return self;
@ -161,17 +161,17 @@ static void *TextFieldSelectionObservingContext = &TextFieldSelectionObservingCo
- (BOOL)textViewShouldBeginEditing:(__unused UITextView *)textView
{
return [_backedTextInput.textInputDelegate textInputShouldBeginEditing];
return [_backedTextInputView.textInputDelegate textInputShouldBeginEditing];
}
- (void)textViewDidBeginEditing:(__unused UITextView *)textView
{
[_backedTextInput.textInputDelegate textInputDidBeginEditing];
[_backedTextInputView.textInputDelegate textInputDidBeginEditing];
}
- (BOOL)textViewShouldEndEditing:(__unused UITextView *)textView
{
return [_backedTextInput.textInputDelegate textInputShouldEndEditing];
return [_backedTextInputView.textInputDelegate textInputShouldEndEditing];
}
- (void)textViewDidEndEditing:(__unused UITextView *)textView
@ -180,24 +180,24 @@ static void *TextFieldSelectionObservingContext = &TextFieldSelectionObservingCo
// iOS does't call `textViewDidChange:` delegate method if the change was happened because of autocorrection
// which was triggered by losing focus. So, we call it manually.
_textDidChangeIsComing = NO;
[_backedTextInput.textInputDelegate textInputDidChange];
[_backedTextInputView.textInputDelegate textInputDidChange];
}
[_backedTextInput.textInputDelegate textInputDidEndEditing];
[_backedTextInputView.textInputDelegate textInputDidEndEditing];
}
- (BOOL)textView:(__unused UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
// Custom implementation of `textInputShouldReturn` and `textInputDidReturn` pair for `UITextView`.
if (!_backedTextInput.textWasPasted && [text isEqualToString:@"\n"]) {
if ([_backedTextInput.textInputDelegate textInputShouldReturn]) {
[_backedTextInput.textInputDelegate textInputDidReturn];
[_backedTextInput endEditing:NO];
if (!_backedTextInputView.textWasPasted && [text isEqualToString:@"\n"]) {
if ([_backedTextInputView.textInputDelegate textInputShouldReturn]) {
[_backedTextInputView.textInputDelegate textInputDidReturn];
[_backedTextInputView endEditing:NO];
return NO;
}
}
BOOL result = [_backedTextInput.textInputDelegate textInputShouldChangeTextInRange:range replacementText:text];
BOOL result = [_backedTextInputView.textInputDelegate textInputShouldChangeTextInRange:range replacementText:text];
if (result) {
_textDidChangeIsComing = YES;
}
@ -207,7 +207,7 @@ static void *TextFieldSelectionObservingContext = &TextFieldSelectionObservingCo
- (void)textViewDidChange:(__unused UITextView *)textView
{
_textDidChangeIsComing = NO;
[_backedTextInput.textInputDelegate textInputDidChange];
[_backedTextInputView.textInputDelegate textInputDidChange];
}
- (void)textViewDidChangeSelection:(__unused UITextView *)textView
@ -226,12 +226,12 @@ static void *TextFieldSelectionObservingContext = &TextFieldSelectionObservingCo
- (void)textViewProbablyDidChangeSelection
{
if ([_backedTextInput.selectedTextRange isEqual:_previousSelectedTextRange]) {
if ([_backedTextInputView.selectedTextRange isEqual:_previousSelectedTextRange]) {
return;
}
_previousSelectedTextRange = _backedTextInput.selectedTextRange;
[_backedTextInput.textInputDelegate textInputDidChangeSelection];
_previousSelectedTextRange = _backedTextInputView.selectedTextRange;
[_backedTextInputView.textInputDelegate textInputDidChangeSelection];
}
@end

View File

@ -11,14 +11,17 @@
@protocol RCTBackedTextInputDelegate;
NS_ASSUME_NONNULL_BEGIN
@protocol RCTBackedTextInputViewProtocol <UITextInput>
@property (nonatomic, copy, nullable) NSString *text;
@property (nonatomic, strong, nullable) UIColor *textColor;
@property (nonatomic, strong, nullable) UIFont *font;
@property (nonatomic, copy, nullable) NSAttributedString *attributedText;
@property (nonatomic, copy, nullable) NSString *placeholder;
@property (nonatomic, strong, nullable) UIColor *placeholderColor;
@property (nonatomic, assign) NSTextAlignment textAlignment;
@property (nonatomic, assign, readonly) BOOL textWasPasted;
@property (nonatomic, strong, nullable) UIFont *font;
@property (nonatomic, assign) UIEdgeInsets textContainerInset;
@property (nonatomic, strong, nullable) UIView *inputAccessoryView;
@property (nonatomic, weak, nullable) id<RCTBackedTextInputDelegate> textInputDelegate;
@ -32,4 +35,11 @@
- (void)setSelectedTextRange:(nullable UITextRange *)selectedTextRange NS_UNAVAILABLE;
- (void)setSelectedTextRange:(nullable UITextRange *)selectedTextRange notifyDelegate:(BOOL)notifyDelegate;
// This protocol disallows direct access to `text` property because
// unwise usage of it can break the `attributeText` behavior.
// Use `attributedText.string` instead.
@property (nonatomic, copy, nullable) NSString *text NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,27 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTBaseTextShadowView.h"
NS_ASSUME_NONNULL_BEGIN
@interface RCTBaseTextInputShadowView : RCTBaseTextShadowView
- (instancetype)initWithBridge:(RCTBridge *)bridge;
@property (nonatomic, copy, nullable) NSString *text;
@property (nonatomic, copy, nullable) NSString *placeholder;
@property (nonatomic, assign) NSInteger maximumNumberOfLines;
@property (nonatomic, copy, nullable) RCTDirectEventBlock onContentSizeChange;
- (void)uiManagerWillPerformMounting;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,251 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTBaseTextInputShadowView.h"
#import <React/RCTBridge.h>
#import <React/RCTShadowView+Layout.h>
#import <React/RCTUIManager.h>
#import <yoga/Yoga.h>
#import "NSTextStorage+FontScaling.h"
#import "RCTBaseTextInputView.h"
@implementation RCTBaseTextInputShadowView
{
__weak RCTBridge *_bridge;
NSAttributedString *_Nullable _previousAttributedText;
BOOL _needsUpdateView;
NSAttributedString *_Nullable _localAttributedText;
CGSize _previousContentSize;
NSTextStorage *_textStorage;
NSTextContainer *_textContainer;
NSLayoutManager *_layoutManager;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if (self = [super init]) {
_bridge = bridge;
_needsUpdateView = YES;
YGNodeSetMeasureFunc(self.yogaNode, RCTBaseTextInputShadowViewMeasure);
}
return self;
}
- (BOOL)isYogaLeafNode
{
return YES;
}
- (void)setLocalData:(NSObject *)localData
{
NSAttributedString *attributedText = (NSAttributedString *)localData;
if ([attributedText isEqualToAttributedString:_localAttributedText]) {
return;
}
_localAttributedText = attributedText;
[self dirtyLayout];
}
- (void)dirtyLayout
{
[super dirtyLayout];
_needsUpdateView = YES;
YGNodeMarkDirty(self.yogaNode);
[self invalidateContentSize];
}
- (void)invalidateContentSize
{
if (!_onContentSizeChange) {
return;
}
CGSize maximumSize = self.frame.size;
if (_maximumNumberOfLines == 1) {
maximumSize.width = CGFLOAT_MAX;
} else {
maximumSize.height = CGFLOAT_MAX;
}
CGSize contentSize = [self sizeThatFitsMinimumSize:(CGSize)CGSizeZero maximumSize:maximumSize];
if (CGSizeEqualToSize(_previousContentSize, contentSize)) {
return;
}
_previousContentSize = contentSize;
_onContentSizeChange(@{
@"contentSize": @{
@"height": @(contentSize.height),
@"width": @(contentSize.width),
},
@"target": self.reactTag,
});
}
#pragma mark - RCTUIManagerObserver
- (void)uiManagerWillPerformMounting
{
if (YGNodeIsDirty(self.yogaNode)) {
return;
}
if (!_needsUpdateView) {
return;
}
_needsUpdateView = NO;
UIEdgeInsets borderInsets = self.borderAsInsets;
UIEdgeInsets paddingInsets = self.paddingAsInsets;
RCTTextAttributes *textAttributes = [self.textAttributes copy];
NSMutableAttributedString *attributedText =
[[NSMutableAttributedString alloc] initWithAttributedString:[self attributedTextWithBaseTextAttributes:nil]];
// Removing all references to Shadow Views and tags to avoid unnececery retainning
// and problems with comparing the strings.
[attributedText removeAttribute:RCTBaseTextShadowViewEmbeddedShadowViewAttributeName
range:NSMakeRange(0, attributedText.length)];
[attributedText removeAttribute:RCTTextAttributesTagAttributeName
range:NSMakeRange(0, attributedText.length)];
if (self.text.length) {
NSAttributedString *propertyAttributedText =
[[NSAttributedString alloc] initWithString:self.text
attributes:self.textAttributes.effectiveTextAttributes];
[attributedText insertAttributedString:propertyAttributedText atIndex:0];
}
BOOL isAttributedTextChanged = NO;
if (![_previousAttributedText isEqualToAttributedString:attributedText]) {
// We have to follow `set prop` pattern:
// If the value has not changed, we must not notify the view about the change,
// otherwise we may break local (temporary) state of the text input.
isAttributedTextChanged = YES;
_previousAttributedText = [attributedText copy];
}
NSNumber *tag = self.reactTag;
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
RCTBaseTextInputView *baseTextInputView = (RCTBaseTextInputView *)viewRegistry[tag];
if (!baseTextInputView) {
return;
}
baseTextInputView.textAttributes = textAttributes;
baseTextInputView.reactBorderInsets = borderInsets;
baseTextInputView.reactPaddingInsets = paddingInsets;
if (isAttributedTextChanged) {
baseTextInputView.attributedText = attributedText;
}
}];
}
#pragma mark -
- (CGSize)sizeThatFitsMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize
{
// Only for the very first render when we don't have `_localAttributedText`,
// we use value directly from the property and/or nested content.
NSAttributedString *attributedText =
_localAttributedText ?: [self attributedTextWithBaseTextAttributes:nil];
if (attributedText.length == 0) {
// It's impossible to measure empty attributed string because all attributes are
// assosiated with some characters, so no characters means no data.
// Placeholder also can represent the intrinsic size when it is visible.
NSString *text = self.placeholder;
if (!text.length) {
// Zero-width space
text = @"\u200B";
}
attributedText = [[NSAttributedString alloc] initWithString:text attributes:self.textAttributes.effectiveTextAttributes];
}
if (!_textStorage) {
_textContainer = [NSTextContainer new];
_textContainer.lineFragmentPadding = 0.0; // Note, the default value is 5.
_layoutManager = [NSLayoutManager new];
[_layoutManager addTextContainer:_textContainer];
_textStorage = [NSTextStorage new];
[_textStorage addLayoutManager:_layoutManager];
}
_textContainer.size = maximumSize;
_textContainer.maximumNumberOfLines = _maximumNumberOfLines;
[_textStorage replaceCharactersInRange:(NSRange){0, _textStorage.length}
withAttributedString:attributedText];
[_layoutManager ensureLayoutForTextContainer:_textContainer];
CGSize size = [_layoutManager usedRectForTextContainer:_textContainer].size;
return (CGSize){
MAX(minimumSize.width, MIN(RCTCeilPixelValue(size.width), maximumSize.width)),
MAX(minimumSize.height, MIN(RCTCeilPixelValue(size.height), maximumSize.height))
};
}
static YGSize RCTBaseTextInputShadowViewMeasure(YGNodeRef node, float width, YGMeasureMode widthMode, float height, YGMeasureMode heightMode)
{
RCTShadowView *shadowView = (__bridge RCTShadowView *)YGNodeGetContext(node);
CGSize minimumSize = CGSizeMake(0, 0);
CGSize maximumSize = CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX);
CGSize size = {
RCTCoreGraphicsFloatFromYogaFloat(width),
RCTCoreGraphicsFloatFromYogaFloat(height)
};
switch (widthMode) {
case YGMeasureModeUndefined:
break;
case YGMeasureModeExactly:
minimumSize.width = size.width;
maximumSize.width = size.width;
break;
case YGMeasureModeAtMost:
maximumSize.width = size.width;
break;
}
switch (heightMode) {
case YGMeasureModeUndefined:
break;
case YGMeasureModeExactly:
minimumSize.height = size.height;
maximumSize.height = size.height;
break;
case YGMeasureModeAtMost:
maximumSize.height = size.height;
break;
}
CGSize measuredSize = [shadowView sizeThatFitsMinimumSize:minimumSize maximumSize:maximumSize];
return (YGSize){
RCTYogaFloatFromCoreGraphicsFloat(measuredSize.width),
RCTYogaFloatFromCoreGraphicsFloat(measuredSize.height)
};
}
@end

View File

@ -11,22 +11,17 @@
#import <React/RCTView.h>
#import "RCTBackedTextInputDelegate.h"
#import "RCTBackedTextInputViewProtocol.h"
#import "RCTFontAttributes.h"
#import "RCTFontAttributesDelegate.h"
@class RCTBridge;
@class RCTEventDispatcher;
@class RCTTextAttributes;
@class RCTTextSelection;
@interface RCTBaseTextInputView : RCTView <RCTFontAttributesDelegate> {
@protected
__weak RCTBridge *_bridge;
RCTEventDispatcher *_eventDispatcher;
NSInteger _nativeEventCount;
NSInteger _mostRecentEventCount;
BOOL _blurOnSubmit;
}
NS_ASSUME_NONNULL_BEGIN
@interface RCTBaseTextInputView : RCTView <RCTBackedTextInputDelegate>
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
@ -36,33 +31,24 @@
@property (nonatomic, readonly) UIView<RCTBackedTextInputViewProtocol> *backedTextInputView;
@property (nonatomic, strong, nullable) RCTTextAttributes *textAttributes;
@property (nonatomic, assign) UIEdgeInsets reactPaddingInsets;
@property (nonatomic, assign) UIEdgeInsets reactBorderInsets;
@property (nonatomic, assign, readonly) CGSize contentSize;
@property (nonatomic, copy) RCTDirectEventBlock onContentSizeChange;
@property (nonatomic, copy) RCTDirectEventBlock onSelectionChange;
@property (nonatomic, readonly, strong) RCTFontAttributes *fontAttributes;
@property (nonatomic, copy, nullable) RCTDirectEventBlock onContentSizeChange;
@property (nonatomic, copy, nullable) RCTDirectEventBlock onSelectionChange;
@property (nonatomic, copy, nullable) RCTDirectEventBlock onChange;
@property (nonatomic, copy, nullable) RCTDirectEventBlock onTextInput;
@property (nonatomic, copy, nullable) RCTDirectEventBlock onScroll;
@property (nonatomic, assign) NSInteger mostRecentEventCount;
@property (nonatomic, assign) BOOL blurOnSubmit;
@property (nonatomic, assign) BOOL selectTextOnFocus;
@property (nonatomic, assign) BOOL clearTextOnFocus;
@property (nonatomic, copy) RCTTextSelection *selection;
- (void)setFont:(UIFont *)font;
- (void)invalidateContentSize;
// Temporary exposure of particial `RCTBackedTextInputDelegate` support.
// In the future all methods of the protocol should move to this class.
- (BOOL)textInputShouldBeginEditing;
- (void)textInputDidBeginEditing;
- (BOOL)textInputShouldReturn;
- (void)textInputDidReturn;
- (void)textInputDidChangeSelection;
- (BOOL)textInputShouldEndEditing;
- (void)textInputDidEndEditing;
@property (nonatomic, strong, nullable) NSNumber *maxLength;
@property (nonatomic, copy) NSAttributedString *attributedText;
@end
NS_ASSUME_NONNULL_END

View File

@ -17,11 +17,15 @@
#import <React/RCTUtils.h>
#import <React/UIView+React.h>
#import "RCTTextAttributes.h"
#import "RCTTextSelection.h"
@implementation RCTBaseTextInputView {
CGSize _previousContentSize;
__weak RCTBridge *_bridge;
__weak RCTEventDispatcher *_eventDispatcher;
BOOL _hasInputAccesoryView;
NSString *_Nullable _predictedText;
NSInteger _nativeEventCount;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
@ -31,8 +35,6 @@
if (self = [super initWithFrame:CGRectZero]) {
_bridge = bridge;
_eventDispatcher = bridge.eventDispatcher;
_fontAttributes = [[RCTFontAttributes alloc] initWithAccessibilityManager:bridge.accessibilityManager];
_fontAttributes.delegate = self;
}
return self;
@ -42,25 +44,39 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)decoder)
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
- (id<RCTBackedTextInputViewProtocol>)backedTextInputView
- (UIView<RCTBackedTextInputViewProtocol> *)backedTextInputView
{
RCTAssert(NO, @"-[RCTBaseTextInputView backedTextInputView] must be implemented in subclass.");
return nil;
}
- (void)setFont:(UIFont *)font
{
self.backedTextInputView.font = font;
[self invalidateContentSize];
}
#pragma mark - RCTComponent
- (void)fontAttributesDidChangeWithFont:(UIFont *)font
- (void)didUpdateReactSubviews
{
self.font = font;
// Do nothing.
}
#pragma mark - Properties
- (void)setTextAttributes:(RCTTextAttributes *)textAttributes
{
_textAttributes = textAttributes;
[self enforceTextAttributesIfNeeded];
}
- (void)enforceTextAttributesIfNeeded
{
id<RCTBackedTextInputViewProtocol> backedTextInputView = self.backedTextInputView;
if (backedTextInputView.attributedText.string.length != 0) {
return;
}
backedTextInputView.font = _textAttributes.effectiveFont;
backedTextInputView.textColor = _textAttributes.effectiveForegroundColor;
backedTextInputView.textAlignment = _textAttributes.alignment;
}
- (void)setReactPaddingInsets:(UIEdgeInsets)reactPaddingInsets
{
_reactPaddingInsets = reactPaddingInsets;
@ -77,12 +93,47 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
[self setNeedsLayout];
}
- (NSAttributedString *)attributedText
{
return self.backedTextInputView.attributedText;
}
- (void)setAttributedText:(NSAttributedString *)attributedText
{
NSInteger eventLag = _nativeEventCount - _mostRecentEventCount;
if (eventLag == 0 && ![attributedText.string isEqualToString:self.backedTextInputView.attributedText.string]) {
UITextRange *selection = self.backedTextInputView.selectedTextRange;
NSInteger oldTextLength = self.backedTextInputView.attributedText.string.length;
self.backedTextInputView.attributedText = attributedText;
if (selection.empty) {
// Maintaining a cursor position relative to the end of the old text.
NSInteger offsetStart =
[self.backedTextInputView offsetFromPosition:self.backedTextInputView.beginningOfDocument
toPosition:selection.start];
NSInteger offsetFromEnd = oldTextLength - offsetStart;
NSInteger newOffset = attributedText.string.length - offsetFromEnd;
UITextPosition *position =
[self.backedTextInputView positionFromPosition:self.backedTextInputView.beginningOfDocument
offset:newOffset];
[self.backedTextInputView setSelectedTextRange:[self.backedTextInputView textRangeFromPosition:position toPosition:position]
notifyDelegate:YES];
}
[self updateLocalData];
} else if (eventLag > RCTTextUpdateLagWarningThreshold) {
RCTLogWarn(@"Native TextInput(%@) is %lld events ahead of JS - try to make your JS faster.", self.backedTextInputView.attributedText.string, (long long)eventLag);
}
}
- (RCTTextSelection *)selection
{
id<RCTBackedTextInputViewProtocol> backedTextInput = self.backedTextInputView;
UITextRange *selectedTextRange = backedTextInput.selectedTextRange;
return [[RCTTextSelection new] initWithStart:[backedTextInput offsetFromPosition:backedTextInput.beginningOfDocument toPosition:selectedTextRange.start]
end:[backedTextInput offsetFromPosition:backedTextInput.beginningOfDocument toPosition:selectedTextRange.end]];
id<RCTBackedTextInputViewProtocol> backedTextInputView = self.backedTextInputView;
UITextRange *selectedTextRange = backedTextInputView.selectedTextRange;
return [[RCTTextSelection new] initWithStart:[backedTextInputView offsetFromPosition:backedTextInputView.beginningOfDocument toPosition:selectedTextRange.start]
end:[backedTextInputView offsetFromPosition:backedTextInputView.beginningOfDocument toPosition:selectedTextRange.end]];
}
- (void)setSelection:(RCTTextSelection *)selection
@ -91,18 +142,18 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
return;
}
id<RCTBackedTextInputViewProtocol> backedTextInput = self.backedTextInputView;
id<RCTBackedTextInputViewProtocol> backedTextInputView = self.backedTextInputView;
UITextRange *previousSelectedTextRange = backedTextInput.selectedTextRange;
UITextPosition *start = [backedTextInput positionFromPosition:backedTextInput.beginningOfDocument offset:selection.start];
UITextPosition *end = [backedTextInput positionFromPosition:backedTextInput.beginningOfDocument offset:selection.end];
UITextRange *selectedTextRange = [backedTextInput textRangeFromPosition:start toPosition:end];
UITextRange *previousSelectedTextRange = backedTextInputView.selectedTextRange;
UITextPosition *start = [backedTextInputView positionFromPosition:backedTextInputView.beginningOfDocument offset:selection.start];
UITextPosition *end = [backedTextInputView positionFromPosition:backedTextInputView.beginningOfDocument offset:selection.end];
UITextRange *selectedTextRange = [backedTextInputView textRangeFromPosition:start toPosition:end];
NSInteger eventLag = _nativeEventCount - _mostRecentEventCount;
if (eventLag == 0 && ![previousSelectedTextRange isEqual:selectedTextRange]) {
[backedTextInput setSelectedTextRange:selectedTextRange notifyDelegate:NO];
[backedTextInputView setSelectedTextRange:selectedTextRange notifyDelegate:NO];
} else if (eventLag > RCTTextUpdateLagWarningThreshold) {
RCTLogWarn(@"Native TextInput(%@) is %lld events ahead of JS - try to make your JS faster.", backedTextInput.text, (long long)eventLag);
RCTLogWarn(@"Native TextInput(%@) is %lld events ahead of JS - try to make your JS faster.", backedTextInputView.attributedText.string, (long long)eventLag);
}
}
@ -116,7 +167,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
- (void)textInputDidBeginEditing
{
if (_clearTextOnFocus) {
self.backedTextInputView.text = @"";
self.backedTextInputView.attributedText = [NSAttributedString new];
}
if (_selectTextOnFocus) {
@ -125,7 +176,27 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
reactTag:self.reactTag
text:self.backedTextInputView.text
text:self.backedTextInputView.attributedText.string
key:nil
eventCount:_nativeEventCount];
}
- (BOOL)textInputShouldEndEditing
{
return YES;
}
- (void)textInputDidEndEditing
{
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd
reactTag:self.reactTag
text:self.backedTextInputView.attributedText.string
key:nil
eventCount:_nativeEventCount];
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur
reactTag:self.reactTag
text:self.backedTextInputView.attributedText.string
key:nil
eventCount:_nativeEventCount];
}
@ -139,7 +210,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
// (no connection to any specific "submitting" process).
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeSubmit
reactTag:self.reactTag
text:self.backedTextInputView.text
text:self.backedTextInputView.attributedText.string
key:nil
eventCount:_nativeEventCount];
@ -151,6 +222,116 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
// Does nothing.
}
- (BOOL)textInputShouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
id<RCTBackedTextInputViewProtocol> backedTextInputView = self.backedTextInputView;
if (!backedTextInputView.textWasPasted) {
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeKeyPress
reactTag:self.reactTag
text:nil
key:text
eventCount:_nativeEventCount];
}
if (_maxLength) {
NSUInteger allowedLength = _maxLength.integerValue - backedTextInputView.attributedText.string.length + range.length;
if (text.length > allowedLength) {
// If we typed/pasted more than one character, limit the text inputted.
if (text.length > 1) {
// Truncate the input string so the result is exactly maxLength
NSString *limitedString = [text substringToIndex:allowedLength];
NSMutableAttributedString *newAttributedText = [backedTextInputView.attributedText mutableCopy];
[newAttributedText replaceCharactersInRange:range withString:limitedString];
backedTextInputView.attributedText = newAttributedText;
_predictedText = newAttributedText.string;
// Collapse selection at end of insert to match normal paste behavior.
UITextPosition *insertEnd = [backedTextInputView positionFromPosition:backedTextInputView.beginningOfDocument
offset:(range.location + allowedLength)];
[backedTextInputView setSelectedTextRange:[backedTextInputView textRangeFromPosition:insertEnd toPosition:insertEnd]
notifyDelegate:YES];
[self textInputDidChange];
}
return NO;
}
}
if (range.location + range.length > _predictedText.length) {
// _predictedText got out of sync in a bad way, so let's just force sync it. Haven't been able to repro this, but
// it's causing a real crash here: #6523822
_predictedText = backedTextInputView.attributedText.string;
}
NSString *previousText = [_predictedText substringWithRange:range] ?: @"";
if (_predictedText) {
_predictedText = [_predictedText stringByReplacingCharactersInRange:range withString:text];
} else {
_predictedText = text;
}
if (_onTextInput) {
_onTextInput(@{
@"text": text,
@"previousText": previousText,
@"range": @{
@"start": @(range.location),
@"end": @(range.location + range.length)
},
@"eventCount": @(_nativeEventCount),
});
}
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange
reactTag:self.reactTag
text:backedTextInputView.attributedText.string
key:nil
eventCount:_nativeEventCount];
return YES;
}
- (void)textInputDidChange
{
[self updateLocalData];
id<RCTBackedTextInputViewProtocol> backedTextInputView = self.backedTextInputView;
// Detect when `backedTextInputView` updates happend that didn't invoke `shouldChangeTextInRange`
// (e.g. typing simplified chinese in pinyin will insert and remove spaces without
// calling shouldChangeTextInRange). This will cause JS to get out of sync so we
// update the mismatched range.
NSRange currentRange;
NSRange predictionRange;
if (findMismatch(backedTextInputView.attributedText.string, _predictedText, &currentRange, &predictionRange)) {
NSString *replacement = [backedTextInputView.attributedText.string substringWithRange:currentRange];
[self textInputShouldChangeTextInRange:predictionRange replacementText:replacement];
// JS will assume the selection changed based on the location of our shouldChangeTextInRange, so reset it.
[self textInputDidChangeSelection];
_predictedText = backedTextInputView.attributedText.string;
}
_nativeEventCount++;
if (_onChange) {
_onChange(@{
@"text": self.attributedText.string,
@"target": self.reactTag,
@"eventCount": @(_nativeEventCount),
});
}
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange
reactTag:self.reactTag
text:backedTextInputView.attributedText.string
key:nil
eventCount:_nativeEventCount];
}
- (void)textInputDidChangeSelection
{
if (!_onSelectionChange) {
@ -158,6 +339,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
}
RCTTextSelection *selection = self.selection;
_onSelectionChange(@{
@"selection": @{
@"start": @(selection.start),
@ -166,59 +348,12 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
});
}
- (BOOL)textInputShouldEndEditing
- (void)updateLocalData
{
return YES;
}
[self enforceTextAttributesIfNeeded];
- (void)textInputDidEndEditing
{
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd
reactTag:self.reactTag
text:self.backedTextInputView.text
key:nil
eventCount:_nativeEventCount];
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur
reactTag:self.reactTag
text:self.backedTextInputView.text
key:nil
eventCount:_nativeEventCount];
}
#pragma mark - Content Size (in Yoga terms, without any insets)
- (CGSize)contentSize
{
CGSize contentSize = self.backedTextInputView.contentSize;
UIEdgeInsets reactPaddingInsets = self.reactPaddingInsets;
contentSize.width -= reactPaddingInsets.left + reactPaddingInsets.right;
contentSize.height -= reactPaddingInsets.top + reactPaddingInsets.bottom;
// Returning value does NOT include border and padding insets.
return contentSize;
}
- (void)invalidateContentSize
{
// Updates `contentSize` property and notifies Yoga about the change, if necessary.
CGSize contentSize = self.contentSize;
if (CGSizeEqualToSize(_previousContentSize, contentSize)) {
return;
}
_previousContentSize = contentSize;
[_bridge.uiManager setIntrinsicContentSize:contentSize forView:self];
if (_onContentSizeChange) {
_onContentSizeChange(@{
@"contentSize": @{
@"height": @(contentSize.height),
@"width": @(contentSize.width),
},
@"target": self.reactTag,
});
}
[_bridge.uiManager setLocalData:[self.backedTextInputView.attributedText copy]
forView:self];
}
#pragma mark - Layout (in UIKit terms, with all insets)
@ -251,12 +386,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
return fittingSize;
}
- (void)layoutSubviews
{
[super layoutSubviews];
[self invalidateContentSize];
}
#pragma mark - Accessibility
- (UIView *)reactAccessibilityElement
@ -343,4 +472,35 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
}
}
#pragma mark - Helpers
static BOOL findMismatch(NSString *first, NSString *second, NSRange *firstRange, NSRange *secondRange)
{
NSInteger firstMismatch = -1;
for (NSUInteger ii = 0; ii < MAX(first.length, second.length); ii++) {
if (ii >= first.length || ii >= second.length || [first characterAtIndex:ii] != [second characterAtIndex:ii]) {
firstMismatch = ii;
break;
}
}
if (firstMismatch == -1) {
return NO;
}
NSUInteger ii = second.length;
NSUInteger lastMismatch = first.length;
while (ii > firstMismatch && lastMismatch > firstMismatch) {
if ([first characterAtIndex:(lastMismatch - 1)] != [second characterAtIndex:(ii - 1)]) {
break;
}
ii--;
lastMismatch--;
}
*firstRange = NSMakeRange(firstMismatch, lastMismatch - firstMismatch);
*secondRange = NSMakeRange(firstMismatch, ii - firstMismatch);
return YES;
}
@end

View File

@ -7,8 +7,8 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <React/RCTViewManager.h>
#import "RCTBaseTextViewManager.h"
@interface RCTBaseTextInputViewManager : RCTViewManager
@interface RCTBaseTextInputViewManager : RCTBaseTextViewManager
@end

View File

@ -9,31 +9,36 @@
#import "RCTBaseTextInputViewManager.h"
#import <React/RCTAccessibilityManager.h>
#import <React/RCTBridge.h>
#import <React/RCTConvert.h>
#import <React/RCTFont.h>
#import <React/RCTShadowView+Layout.h>
#import <React/RCTShadowView.h>
#import <React/RCTUIManager.h>
#import <React/RCTUIManagerObserverCoordinator.h>
#import "RCTConvert+Text.h"
#import "RCTBaseTextInputShadowView.h"
#import "RCTBaseTextInputView.h"
#import "RCTConvert+Text.h"
@interface RCTBaseTextInputViewManager () <RCTUIManagerObserver>
@end
@implementation RCTBaseTextInputViewManager
{
NSHashTable<RCTBaseTextInputShadowView *> *_shadowViews;
}
RCT_EXPORT_MODULE()
#pragma mark - Unified <TextInput> properties
RCT_REMAP_VIEW_PROPERTY(allowFontScaling, fontAttributes.allowFontScaling, BOOL)
RCT_REMAP_VIEW_PROPERTY(autoCapitalize, backedTextInputView.autocapitalizationType, UITextAutocapitalizationType)
RCT_REMAP_VIEW_PROPERTY(autoCorrect, backedTextInputView.autocorrectionType, UITextAutocorrectionType)
RCT_REMAP_VIEW_PROPERTY(color, backedTextInputView.textColor, UIColor)
RCT_REMAP_VIEW_PROPERTY(editable, backedTextInputView.editable, BOOL)
RCT_REMAP_VIEW_PROPERTY(enablesReturnKeyAutomatically, backedTextInputView.enablesReturnKeyAutomatically, BOOL)
RCT_REMAP_VIEW_PROPERTY(fontSize, fontAttributes.fontSize, NSNumber)
RCT_REMAP_VIEW_PROPERTY(fontWeight, fontAttributes.fontWeight, NSString)
RCT_REMAP_VIEW_PROPERTY(fontStyle, fontAttributes.fontStyle, NSString)
RCT_REMAP_VIEW_PROPERTY(fontFamily, fontAttributes.fontFamily, NSString)
RCT_REMAP_VIEW_PROPERTY(keyboardAppearance, backedTextInputView.keyboardAppearance, UIKeyboardAppearance)
RCT_REMAP_VIEW_PROPERTY(keyboardType, backedTextInputView.keyboardType, UIKeyboardType)
RCT_REMAP_VIEW_PROPERTY(placeholder, backedTextInputView.placeholder, NSString)
@ -42,26 +47,74 @@ RCT_REMAP_VIEW_PROPERTY(returnKeyType, backedTextInputView.returnKeyType, UIRetu
RCT_REMAP_VIEW_PROPERTY(secureTextEntry, backedTextInputView.secureTextEntry, BOOL)
RCT_REMAP_VIEW_PROPERTY(selectionColor, backedTextInputView.tintColor, UIColor)
RCT_REMAP_VIEW_PROPERTY(spellCheck, backedTextInputView.spellCheckingType, UITextSpellCheckingType)
RCT_REMAP_VIEW_PROPERTY(textAlign, backedTextInputView.textAlignment, NSTextAlignment)
RCT_REMAP_VIEW_PROPERTY(caretHidden, backedTextInputView.caretHidden, BOOL)
RCT_REMAP_VIEW_PROPERTY(clearButtonMode, backedTextInputView.clearButtonMode, UITextFieldViewMode)
RCT_EXPORT_VIEW_PROPERTY(blurOnSubmit, BOOL)
RCT_EXPORT_VIEW_PROPERTY(clearTextOnFocus, BOOL)
RCT_EXPORT_VIEW_PROPERTY(maxLength, NSNumber)
RCT_EXPORT_VIEW_PROPERTY(selectTextOnFocus, BOOL)
RCT_EXPORT_VIEW_PROPERTY(selection, RCTTextSelection)
RCT_EXPORT_VIEW_PROPERTY(text, NSString)
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onTextInput, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(mostRecentEventCount, NSInteger)
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowView *)shadowView
RCT_EXPORT_SHADOW_PROPERTY(text, NSString)
RCT_EXPORT_SHADOW_PROPERTY(placeholder, NSString)
RCT_EXPORT_SHADOW_PROPERTY(onContentSizeChange, RCTBubblingEventBlock)
- (RCTShadowView *)shadowView
{
NSNumber *reactTag = shadowView.reactTag;
UIEdgeInsets borderAsInsets = shadowView.borderAsInsets;
UIEdgeInsets paddingAsInsets = shadowView.paddingAsInsets;
return ^(RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTBaseTextInputView *> *viewRegistry) {
RCTBaseTextInputView *view = viewRegistry[reactTag];
view.reactBorderInsets = borderAsInsets;
view.reactPaddingInsets = paddingAsInsets;
};
RCTBaseTextInputShadowView *shadowView = [[RCTBaseTextInputShadowView alloc] initWithBridge:self.bridge];
shadowView.textAttributes.fontSizeMultiplier = self.bridge.accessibilityManager.multiplier;
[_shadowViews addObject:shadowView];
return shadowView;
}
- (void)setBridge:(RCTBridge *)bridge
{
[super setBridge:bridge];
_shadowViews = [NSHashTable weakObjectsHashTable];
[bridge.uiManager.observerCoordinator addObserver:self];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleDidUpdateMultiplierNotification)
name:RCTAccessibilityManagerDidUpdateMultiplierNotification
object:bridge.accessibilityManager];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - RCTUIManagerObserver
- (void)uiManagerWillPerformMounting:(__unused RCTUIManager *)uiManager
{
for (RCTBaseTextInputShadowView *shadowView in _shadowViews) {
[shadowView uiManagerWillPerformMounting];
}
}
#pragma mark - Font Size Multiplier
- (void)handleDidUpdateMultiplierNotification
{
CGFloat fontSizeMultiplier = self.bridge.accessibilityManager.multiplier;
for (RCTBaseTextInputShadowView *shadowView in _shadowViews) {
shadowView.textAttributes.fontSizeMultiplier = fontSizeMultiplier;
[shadowView dirtyLayout];
}
[self.bridge.uiManager setNeedsLayout];
}
@end

View File

@ -7,18 +7,12 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
#import <React/RCTComponent.h>
#import <React/RCTView.h>
#import "RCTBaseTextInputView.h"
@class RCTUITextField;
NS_ASSUME_NONNULL_BEGIN
@interface RCTSinglelineTextInputView : RCTBaseTextInputView
@property (nonatomic, assign) BOOL caretHidden;
@property (nonatomic, strong) NSNumber *maxLength;
@end
NS_ASSUME_NONNULL_END

View File

@ -10,133 +10,36 @@
#import "RCTSinglelineTextInputView.h"
#import <React/RCTBridge.h>
#import <React/RCTConvert.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTFont.h>
#import <React/RCTUIManager.h>
#import <React/RCTUtils.h>
#import <React/UIView+React.h>
#import "RCTBackedTextInputDelegate.h"
#import "RCTTextSelection.h"
#import "RCTUITextField.h"
@interface RCTSinglelineTextInputView () <RCTBackedTextInputDelegate>
@end
@implementation RCTSinglelineTextInputView
{
RCTUITextField *_backedTextInput;
BOOL _submitted;
CGSize _previousContentSize;
RCTUITextField *_backedTextInputView;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if (self = [super initWithBridge:bridge]) {
// `blurOnSubmit` defaults to `true` for <TextInput multiline={false}> by design.
_blurOnSubmit = YES;
self.blurOnSubmit = YES;
_backedTextInput = [[RCTUITextField alloc] initWithFrame:self.bounds];
_backedTextInput.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_backedTextInput.textInputDelegate = self;
_backedTextInput.font = self.fontAttributes.font;
_backedTextInputView = [[RCTUITextField alloc] initWithFrame:self.bounds];
_backedTextInputView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_backedTextInputView.textInputDelegate = self;
[self addSubview:_backedTextInput];
[self addSubview:_backedTextInputView];
}
return self;
}
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)coder)
- (id<RCTBackedTextInputViewProtocol>)backedTextInputView
{
return _backedTextInput;
}
- (void)sendKeyValueForString:(NSString *)string
{
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeKeyPress
reactTag:self.reactTag
text:nil
key:string
eventCount:_nativeEventCount];
}
#pragma mark - Properties
- (NSString *)text
{
return _backedTextInput.text;
}
- (void)setText:(NSString *)text
{
NSInteger eventLag = _nativeEventCount - _mostRecentEventCount;
if (eventLag == 0 && ![text isEqualToString:self.text]) {
UITextRange *selection = _backedTextInput.selectedTextRange;
NSInteger oldTextLength = _backedTextInput.text.length;
_backedTextInput.text = text;
if (selection.empty) {
// maintain cursor position relative to the end of the old text
NSInteger offsetStart = [_backedTextInput offsetFromPosition:_backedTextInput.beginningOfDocument toPosition:selection.start];
NSInteger offsetFromEnd = oldTextLength - offsetStart;
NSInteger newOffset = text.length - offsetFromEnd;
UITextPosition *position = [_backedTextInput positionFromPosition:_backedTextInput.beginningOfDocument offset:newOffset];
[_backedTextInput setSelectedTextRange:[_backedTextInput textRangeFromPosition:position toPosition:position]
notifyDelegate:YES];
}
} else if (eventLag > RCTTextUpdateLagWarningThreshold) {
RCTLogWarn(@"Native TextInput(%@) is %lld events ahead of JS - try to make your JS faster.", _backedTextInput.text, (long long)eventLag);
}
}
#pragma mark - RCTBackedTextInputDelegate
- (BOOL)textInputShouldChangeTextInRange:(NSRange)range replacementText:(NSString *)string
{
// Only allow single keypresses for `onKeyPress`, pasted text will not be sent.
if (!_backedTextInput.textWasPasted) {
[self sendKeyValueForString:string];
}
if (_maxLength != nil && ![string isEqualToString:@"\n"]) { // Make sure forms can be submitted via return.
NSUInteger allowedLength = _maxLength.integerValue - MIN(_maxLength.integerValue, _backedTextInput.text.length) + range.length;
if (string.length > allowedLength) {
if (string.length > 1) {
// Truncate the input string so the result is exactly `maxLength`.
NSString *limitedString = [string substringToIndex:allowedLength];
NSMutableString *newString = _backedTextInput.text.mutableCopy;
[newString replaceCharactersInRange:range withString:limitedString];
_backedTextInput.text = newString;
// Collapse selection at end of insert to match normal paste behavior.
UITextPosition *insertEnd = [_backedTextInput positionFromPosition:_backedTextInput.beginningOfDocument
offset:(range.location + allowedLength)];
[_backedTextInput setSelectedTextRange:[_backedTextInput textRangeFromPosition:insertEnd toPosition:insertEnd]
notifyDelegate:YES];
[self textInputDidChange];
}
return NO;
}
}
return YES;
}
- (void)textInputDidChange
{
_nativeEventCount++;
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange
reactTag:self.reactTag
text:_backedTextInput.text
key:nil
eventCount:_nativeEventCount];
return _backedTextInputView;
}
@end

View File

@ -9,6 +9,10 @@
#import "RCTBaseTextInputViewManager.h"
NS_ASSUME_NONNULL_BEGIN
@interface RCTSinglelineTextInputViewManager : RCTBaseTextInputViewManager
@end
NS_ASSUME_NONNULL_END

View File

@ -9,15 +9,8 @@
#import "RCTSinglelineTextInputViewManager.h"
#import <React/RCTBridge.h>
#import <React/RCTFont.h>
#import <React/RCTShadowView+Layout.h>
#import <React/RCTShadowView.h>
#import "RCTConvert+Text.h"
#import "RCTSinglelineTextInputShadowView.h"
#import "RCTBaseTextInputShadowView.h"
#import "RCTSinglelineTextInputView.h"
#import "RCTUITextField.h"
@implementation RCTSinglelineTextInputViewManager
@ -25,7 +18,12 @@ RCT_EXPORT_MODULE()
- (RCTShadowView *)shadowView
{
return [RCTSinglelineTextInputShadowView new];
RCTBaseTextInputShadowView *shadowView =
(RCTBaseTextInputShadowView *)[super shadowView];
shadowView.maximumNumberOfLines = 1;
return shadowView;
}
- (UIView *)view
@ -33,10 +31,4 @@ RCT_EXPORT_MODULE()
return [[RCTSinglelineTextInputView alloc] initWithBridge:self.bridge];
}
#pragma mark - Singleline <TextInput> (aka TextField) specific properties
RCT_REMAP_VIEW_PROPERTY(caretHidden, backedTextInputView.caretHidden, BOOL)
RCT_REMAP_VIEW_PROPERTY(clearButtonMode, backedTextInputView.clearButtonMode, UITextFieldViewMode)
RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock)
@end

View File

@ -7,8 +7,8 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <React/RCTShadowView.h>
#import "RCTBaseTextShadowView.h"
@interface RCTMultilineTextInputShadowView : RCTShadowView
@interface RCTVirtualTextShadowView : RCTBaseTextShadowView
@end

View File

@ -0,0 +1,75 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTVirtualTextShadowView.h"
#import <React/RCTShadowView+Layout.h>
#import <yoga/Yoga.h>
#import "RCTRawTextShadowView.h"
@implementation RCTVirtualTextShadowView {
BOOL _isLayoutDirty;
}
#pragma mark - Life Cycle
- (void)insertReactSubview:(RCTShadowView *)subview atIndex:(NSInteger)index
{
[super insertReactSubview:subview atIndex:index];
[self dirtyLayout];
if (![subview isKindOfClass:[RCTVirtualTextShadowView class]]) {
YGNodeSetDirtiedFunc(subview.yogaNode, RCTVirtualTextShadowViewYogaNodeDirtied);
}
}
- (void)removeReactSubview:(RCTShadowView *)subview
{
if (![subview isKindOfClass:[RCTVirtualTextShadowView class]]) {
YGNodeSetDirtiedFunc(subview.yogaNode, NULL);
}
[self dirtyLayout];
[super removeReactSubview:subview];
}
#pragma mark - Layout
- (void)dirtyLayout
{
[super dirtyLayout];
if (_isLayoutDirty) {
return;
}
_isLayoutDirty = YES;
[self.superview dirtyLayout];
}
- (void)clearLayout
{
_isLayoutDirty = NO;
}
static void RCTVirtualTextShadowViewYogaNodeDirtied(YGNodeRef node)
{
RCTShadowView *shadowView = (__bridge RCTShadowView *)YGNodeGetContext(node);
RCTVirtualTextShadowView *virtualTextShadowView =
(RCTVirtualTextShadowView *)shadowView.reactSuperview;
[virtualTextShadowView dirtyLayout];
}
@end

View File

@ -7,8 +7,8 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <React/RCTShadowView.h>
#import "RCTBaseTextViewManager.h"
@interface RCTSinglelineTextInputShadowView : RCTShadowView
@interface RCTVirtualTextViewManager : RCTBaseTextViewManager
@end

View File

@ -7,13 +7,22 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTSinglelineTextInputShadowView.h"
#import "RCTVirtualTextViewManager.h"
@implementation RCTSinglelineTextInputShadowView
#import "RCTVirtualTextShadowView.h"
- (BOOL)isYogaLeafNode
@implementation RCTVirtualTextViewManager
RCT_EXPORT_MODULE(RCTVirtualText)
- (UIView *)view
{
return YES;
return [UIView new];
}
- (RCTShadowView *)shadowView
{
return [RCTVirtualTextShadowView new];
}
@end

View File

@ -233,20 +233,8 @@ exports.examples = [
render: function() {
return (
<Text>
The text
<View style={{borderColor: 'red', borderWidth: 1}}>
<Text style={{borderColor: 'blue', borderWidth: 1}}>Text Inside</Text>
<Text style={{borderColor: 'green', borderWidth: 1}}>Another text Inside</Text>
<Text style={{borderColor: 'yellow', borderWidth: 1}}>
Total inseption
<View style={{borderColor: 'red', borderWidth: 1}}>
<Text style={{borderColor: 'blue', borderWidth: 1}}>Insepted Text Inside</Text>
</View>
</Text>
</View>
The text should wrap if it goes on multiple lines. See, this is going
to the next line.
The text after.
</Text>
);
},
@ -759,6 +747,27 @@ exports.examples = [
);
},
},
{
title: 'Nested content',
render: function() {
return (
<Text>
This text has a view
<View style={{borderColor: 'red', borderWidth: 1}}>
<Text style={{borderColor: 'blue', borderWidth: 1}}>which has</Text>
<Text style={{borderColor: 'green', borderWidth: 1}}>another text inside.</Text>
<Text style={{borderColor: 'yellow', borderWidth: 1}}>
And moreover, it has another view
<View style={{borderColor: 'red', borderWidth: 1}}>
<Text style={{borderColor: 'blue', borderWidth: 1}}>with another text inside!</Text>
</View>
</Text>
</View>
Because we need to go deeper.
</Text>
);
},
},
{
title: 'Dynamic Font Size Adjustment',
render: function(): React.Element<any> {

View File

@ -18,6 +18,8 @@ var {
TextInput,
View,
StyleSheet,
Slider,
Switch,
} = ReactNative;
class WithLabel extends React.Component<$FlowFixMeProps> {
@ -335,6 +337,61 @@ class SelectionExample extends React.Component<$FlowFixMeProps, SelectionExample
}
}
class AutogrowingTextInputExample extends React.Component<$FlowFixMeProps, $FlowFixMeState> {
constructor(props) {
super(props);
this.state = {
width: 100,
multiline: true,
text: '',
contentSize: {
width: 0,
height: 0,
},
};
}
componentWillReceiveProps(props) {
this.setState({
multiline: props.multiline,
});
}
render() {
var {style, multiline, ...props} = this.props;
return (
<View>
<Text>Width:</Text>
<Slider
value={100}
minimumValue={0}
maximumValue={100}
step={10}
onValueChange={(value) => this.setState({width: value})}
/>
<Text>Multiline:</Text>
<Switch
value={this.state.multiline}
onValueChange={(value) => this.setState({multiline: value})}
/>
<Text>TextInput:</Text>
<TextInput
value="prop"
multiline={this.state.multiline}
style={[style, {width: this.state.width + '%'}]}
onChangeText={(value) => this.setState({text: value})}
onContentSizeChange={(event) => this.setState({contentSize: event.nativeEvent.contentSize})}
{...props}
/>
<Text>Plain text value representation:</Text>
<Text>{this.state.text}</Text>
<Text>Content Size: {JSON.stringify(this.state.contentSize)}</Text>
</View>
);
}
}
var styles = StyleSheet.create({
page: {
paddingBottom: 300,
@ -478,6 +535,29 @@ exports.examples = [
);
}
},
{
title: 'Nested content and `value` property',
render: function() {
return (
<View>
<WithLabel label="singleline">
<TextInput style={styles.default} value="(value property)">
(first raw text node)
<Text color="red">(internal raw text node)</Text>
(last raw text node)
</TextInput>
</WithLabel>
<WithLabel label="multiline">
<TextInput style={styles.default} multiline={true} value="(value property)">
(first raw text node)
<Text color="red">(internal raw text node)</Text>
(last raw text node)
</TextInput>
</WithLabel>
</View>
);
}
},
{
title: 'Keyboard types',
render: function() {
@ -854,6 +934,34 @@ exports.examples = [
);
}
},
{
title: 'Auto-expanding',
render: function() {
return (
<View>
<AutogrowingTextInputExample
enablesReturnKeyAutomatically={true}
returnKeyType="done"
multiline={true}
style={{maxHeight: 400, minHeight: 20, paddingTop: 0, backgroundColor: '#eeeeee', color: 'blue'}}
>
<Text style={{fontSize: 30, color: 'green'}}>
huge
</Text>
generic generic generic
<Text style={{fontSize: 6, color: 'red'}}>
small small small small small small
</Text>
<Text>regular regular</Text>
<Text style={{fontSize: 30, color: 'green'}}>
huge huge huge huge huge
</Text>
generic generic generic
</AutogrowingTextInputExample>
</View>
);
}
},
{
title: 'Attributed text',
render: function() {

View File

@ -59,7 +59,8 @@ RCT_EXPORT_MODULE()
- (instancetype)init
{
if ((self = [super init])) {
if (self = [super init]) {
_multiplier = 1.0;
// TODO: can this be moved out of the startup path?
[[NSNotificationCenter defaultCenter] addObserver:self

View File

@ -518,23 +518,12 @@ static NSDictionary *deviceOrientationEventBody(UIDeviceOrientation orientation)
}
}
// These are blocks to be executed on each view, immediately after
// reactSetFrame: has been called. Note that if reactSetFrame: is not called,
// these won't be called either, so this is not a suitable place to update
// properties that aren't related to layout.
NSMutableDictionary<NSNumber *, RCTViewManagerUIBlock> *updateBlocks =
[NSMutableDictionary new];
for (RCTShadowView *shadowView in viewsWithNewFrames) {
// We have to do this after we build the parentsAreNew array.
shadowView.newView = NO;
NSNumber *reactTag = shadowView.reactTag;
RCTViewManager *manager = [_componentDataByName[shadowView.viewName] manager];
RCTViewManagerUIBlock block = [manager uiBlockToAmendWithShadowView:shadowView];
if (block) {
updateBlocks[reactTag] = block;
}
if (shadowView.onLayout) {
CGRect frame = shadowView.frame;
@ -607,7 +596,6 @@ static NSDictionary *deviceOrientationEventBody(UIDeviceOrientation orientation)
view.reactLayoutDirection = layoutDirection;
}
RCTViewManagerUIBlock updateBlock = updateBlocks[reactTag];
if (creatingLayoutAnimation) {
// Animate view creation
@ -632,9 +620,6 @@ static NSDictionary *deviceOrientationEventBody(UIDeviceOrientation orientation)
} else if ([property isEqualToString:@"opacity"]) {
view.layer.opacity = finalOpacity;
}
if (updateBlock) {
updateBlock(self, viewRegistry);
}
} withCompletionBlock:completion];
} else if (updatingLayoutAnimation) {
@ -642,18 +627,12 @@ static NSDictionary *deviceOrientationEventBody(UIDeviceOrientation orientation)
// Animate view update
[updatingLayoutAnimation performAnimations:^{
[view reactSetFrame:frame];
if (updateBlock) {
updateBlock(self, viewRegistry);
}
} withCompletionBlock:completion];
} else {
// Update without animation
[view reactSetFrame:frame];
if (updateBlock) {
updateBlock(self, viewRegistry);
}
completion(YES);
}
}
@ -663,20 +642,6 @@ static NSDictionary *deviceOrientationEventBody(UIDeviceOrientation orientation)
};
}
- (void)_amendPendingUIBlocksWithStylePropagationUpdateForShadowView:(RCTShadowView *)topView
{
NSMutableSet<RCTApplierBlock> *applierBlocks = [NSMutableSet setWithCapacity:1];
[topView collectUpdatedProperties:applierBlocks parentProperties:@{}];
if (applierBlocks.count) {
[self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
for (RCTApplierBlock block in applierBlocks) {
block(viewRegistry);
}
}];
}
}
/**
* A method to be called from JS, which takes a container ID and then releases
* all subviews for that container upon receipt.
@ -1078,13 +1043,6 @@ RCT_EXPORT_METHOD(dispatchViewManagerCommand:(nonnull NSNumber *)reactTag
*/
- (void)_layoutAndMount
{
// Gather blocks to be executed now that all view hierarchy manipulations have
// been completed (note that these may still take place before layout has finished)
for (RCTComponentData *componentData in _componentDataByName.allValues) {
RCTViewManagerUIBlock uiBlock = [componentData uiBlockToAmendWithShadowViewRegistry:_shadowViewRegistry];
[self addUIBlock:uiBlock];
}
[self _dispatchPropsDidChangeEvents];
[self _dispatchChildrenDidChangeEvents];
@ -1098,12 +1056,6 @@ RCT_EXPORT_METHOD(dispatchViewManagerCommand:(nonnull NSNumber *)reactTag
[_observerCoordinator uiManagerDidPerformLayout:self];
// Properies propagation
for (NSNumber *reactTag in _rootViewTags) {
RCTRootShadowView *rootView = (RCTRootShadowView *)_shadowViewRegistry[reactTag];
[self _amendPendingUIBlocksWithStylePropagationUpdateForShadowView:rootView];
}
[_observerCoordinator uiManagerWillPerformMounting:self];
[self flushUIBlocksWithCompletion:^{

View File

@ -33,6 +33,4 @@
- (NSDictionary<NSString *, id> *)viewConfig;
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(NSDictionary<NSNumber *, RCTShadowView *> *)registry;
@end

View File

@ -37,7 +37,6 @@ static SEL selectorForType(NSString *type)
id<RCTComponent> _defaultView; // Only needed for RCT_CUSTOM_VIEW_PROPERTY
RCTPropBlockDictionary *_viewPropBlocks;
RCTPropBlockDictionary *_shadowPropBlocks;
BOOL _implementsUIBlockToAmendWithShadowViewRegistry;
__weak RCTBridge *_bridge;
}
@ -53,14 +52,6 @@ static SEL selectorForType(NSString *type)
_shadowPropBlocks = [NSMutableDictionary new];
_name = moduleNameForClass(managerClass);
_implementsUIBlockToAmendWithShadowViewRegistry = NO;
Class cls = _managerClass;
while (cls != [RCTViewManager class]) {
_implementsUIBlockToAmendWithShadowViewRegistry = _implementsUIBlockToAmendWithShadowViewRegistry ||
RCTClassOverridesInstanceMethod(cls, @selector(uiBlockToAmendWithShadowViewRegistry:));
cls = [cls superclass];
}
}
return self;
}
@ -437,14 +428,6 @@ static RCTPropBlock createNSInvocationSetter(NSMethodSignature *typeSignature, S
};
}
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(NSDictionary<NSNumber *, RCTShadowView *> *)registry
{
if (_implementsUIBlockToAmendWithShadowViewRegistry) {
return [[self manager] uiBlockToAmendWithShadowViewRegistry:registry];
}
return nil;
}
static NSString *moduleNameForClass(Class managerClass)
{
// Hackety hack, this partially re-implements RCTBridgeModuleNameForClass

View File

@ -226,6 +226,11 @@ typedef void (^RCTApplierBlock)(NSDictionary<NSNumber *, UIView *> *viewRegistry
viewsWithNewFrame:(NSMutableSet<RCTShadowView *> *)viewsWithNewFrame
absolutePosition:(CGPoint)absolutePosition NS_REQUIRES_SUPER;
- (void)applyLayoutWithFrame:(CGRect)frame
layoutDirection:(UIUserInterfaceLayoutDirection)layoutDirection
viewsWithUpdatedLayout:(NSMutableSet<RCTShadowView *> *)viewsWithUpdatedLayout
absolutePosition:(CGPoint)absolutePosition;
/**
* Enumerate the child nodes and tell them to apply layout.
*/
@ -253,13 +258,6 @@ typedef void (^RCTApplierBlock)(NSDictionary<NSNumber *, UIView *> *viewRegistry
*/
- (BOOL)isYogaLeafNode;
- (void)dirtyPropagation NS_REQUIRES_SUPER;
- (BOOL)isPropagationDirty;
- (void)dirtyText NS_REQUIRES_SUPER;
- (void)setTextComputed NS_REQUIRES_SUPER;
- (BOOL)isTextDirty;
/**
* As described in RCTComponent protocol.
*/

View File

@ -213,38 +213,53 @@ static void RCTProcessMetaPropsBorder(const YGValue metaProps[META_PROP_COUNT],
}
#endif
CGRect frame = CGRectMake(YGNodeLayoutGetLeft(node), YGNodeLayoutGetTop(node), YGNodeLayoutGetWidth(node), YGNodeLayoutGetHeight(node));
// Even if `YGNodeLayoutGetDirection` can return `YGDirectionInherit` here, it actually means
// that Yoga will use LTR layout for the view (even if layout process is not finished yet).
UIUserInterfaceLayoutDirection layoutDirection = YGNodeLayoutGetDirection(_yogaNode) == YGDirectionRTL ? UIUserInterfaceLayoutDirectionRightToLeft : UIUserInterfaceLayoutDirectionLeftToRight;
[self applyLayoutWithFrame:frame
layoutDirection:layoutDirection
viewsWithUpdatedLayout:viewsWithNewFrame
absolutePosition:absolutePosition];
}
- (void)applyLayoutWithFrame:(CGRect)frame
layoutDirection:(UIUserInterfaceLayoutDirection)layoutDirection
viewsWithUpdatedLayout:(NSMutableSet<RCTShadowView *> *)viewsWithUpdatedLayout
absolutePosition:(CGPoint)absolutePosition
{
CGPoint absoluteTopLeft = {
absolutePosition.x + YGNodeLayoutGetLeft(node),
absolutePosition.y + YGNodeLayoutGetTop(node)
absolutePosition.x + frame.origin.x,
absolutePosition.y + frame.origin.y
};
CGPoint absoluteBottomRight = {
absolutePosition.x + YGNodeLayoutGetLeft(node) + YGNodeLayoutGetWidth(node),
absolutePosition.y + YGNodeLayoutGetTop(node) + YGNodeLayoutGetHeight(node)
absolutePosition.x + frame.origin.x + frame.size.width,
absolutePosition.y + frame.origin.y + frame.size.height
};
CGRect frame = {{
RCTRoundPixelValue(YGNodeLayoutGetLeft(node)),
RCTRoundPixelValue(YGNodeLayoutGetTop(node)),
CGRect roundedFrame = {{
RCTRoundPixelValue(frame.origin.x),
RCTRoundPixelValue(frame.origin.y),
}, {
RCTRoundPixelValue(absoluteBottomRight.x - absoluteTopLeft.x),
RCTRoundPixelValue(absoluteBottomRight.y - absoluteTopLeft.y)
}};
// Even if `YGNodeLayoutGetDirection` can return `YGDirectionInherit` here, it actually means
// that Yoga will use LTR layout for the view (even if layout process is not finished yet).
UIUserInterfaceLayoutDirection updatedLayoutDirection = YGNodeLayoutGetDirection(_yogaNode) == YGDirectionRTL ? UIUserInterfaceLayoutDirectionRightToLeft : UIUserInterfaceLayoutDirectionLeftToRight;
if (!CGRectEqualToRect(frame, _frame) || _layoutDirection != updatedLayoutDirection) {
_frame = frame;
_layoutDirection = updatedLayoutDirection;
[viewsWithNewFrame addObject:self];
if (!CGRectEqualToRect(_frame, roundedFrame) || _layoutDirection != layoutDirection) {
_frame = roundedFrame;
_layoutDirection = layoutDirection;
[viewsWithUpdatedLayout addObject:self];
}
absolutePosition.x += YGNodeLayoutGetLeft(node);
absolutePosition.y += YGNodeLayoutGetTop(node);
absolutePosition.x += frame.origin.x;
absolutePosition.y += frame.origin.y;
[self applyLayoutToChildren:node viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition];
[self applyLayoutToChildren:_yogaNode
viewsWithNewFrame:viewsWithUpdatedLayout
absolutePosition:absolutePosition];
}
- (void)applyLayoutToChildren:(YGNodeRef)node
@ -381,37 +396,6 @@ static void RCTProcessMetaPropsBorder(const YGValue metaProps[META_PROP_COUNT],
return NO;
}
- (void)dirtyPropagation
{
if (_propagationLifecycle != RCTUpdateLifecycleDirtied) {
_propagationLifecycle = RCTUpdateLifecycleDirtied;
[_superview dirtyPropagation];
}
}
- (BOOL)isPropagationDirty
{
return _propagationLifecycle != RCTUpdateLifecycleComputed;
}
- (void)dirtyText
{
if (_textLifecycle != RCTUpdateLifecycleDirtied) {
_textLifecycle = RCTUpdateLifecycleDirtied;
[_superview dirtyText];
}
}
- (BOOL)isTextDirty
{
return _textLifecycle != RCTUpdateLifecycleComputed;
}
- (void)setTextComputed
{
_textLifecycle = RCTUpdateLifecycleComputed;
}
- (void)insertReactSubview:(RCTShadowView *)subview atIndex:(NSInteger)atIndex
{
RCTAssert(self.canHaveSubviews, @"Attempt to insert subview inside leaf view.");
@ -421,14 +405,10 @@ static void RCTProcessMetaPropsBorder(const YGValue metaProps[META_PROP_COUNT],
YGNodeInsertChild(_yogaNode, subview.yogaNode, (uint32_t)atIndex);
}
subview->_superview = self;
[self dirtyText];
[self dirtyPropagation];
}
- (void)removeReactSubview:(RCTShadowView *)subview
{
[subview dirtyText];
[subview dirtyPropagation];
subview->_superview = nil;
[_reactSubviews removeObject:subview];
if (![self isYogaLeafNode]) {
@ -560,7 +540,6 @@ RCT_BORDER_PROPERTY(End, END)
- (void)set##setProp:(YGValue)value \
{ \
RCT_SET_YGVALUE_AUTO(value, YGNodeStyleSet##cssProp, _yogaNode); \
[self dirtyText]; \
} \
- (YGValue)getProp \
{ \
@ -571,7 +550,6 @@ RCT_BORDER_PROPERTY(End, END)
- (void)set##setProp:(YGValue)value \
{ \
RCT_SET_YGVALUE(value, YGNodeStyleSet##cssProp, _yogaNode); \
[self dirtyText]; \
} \
- (YGValue)getProp \
{ \
@ -591,7 +569,6 @@ RCT_MIN_MAX_DIMENSION_PROPERTY(MaxHeight, maxHeight, MaxHeight)
- (void)set##setProp:(YGValue)value \
{ \
RCT_SET_YGVALUE(value, YGNodeStyleSetPosition, _yogaNode, edge); \
[self dirtyText]; \
} \
- (YGValue)getProp \
{ \
@ -608,7 +585,6 @@ RCT_POSITION_PROPERTY(End, end, YGEdgeEnd)
{
YGEdge edge = [[RCTI18nUtil sharedInstance] doLeftAndRightSwapInRTL] ? YGEdgeStart : YGEdgeLeft;
RCT_SET_YGVALUE(value, YGNodeStyleSetPosition, _yogaNode, edge);
[self dirtyText];
}
- (YGValue)left
{
@ -620,7 +596,6 @@ RCT_POSITION_PROPERTY(End, end, YGEdgeEnd)
{
YGEdge edge = [[RCTI18nUtil sharedInstance] doLeftAndRightSwapInRTL] ? YGEdgeEnd : YGEdgeRight;
RCT_SET_YGVALUE(value, YGNodeStyleSetPosition, _yogaNode, edge);
[self dirtyText];
}
- (YGValue)right
{

View File

@ -63,20 +63,6 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, NSDictionary<NSNu
*/
- (NSArray<NSString *> *)customBubblingEventTypes __deprecated_msg("Use RCTBubblingEventBlock props instead.");
/**
* Called to notify manager that layout has finished, in case any calculated
* properties need to be copied over from shadow view to view.
*/
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowView *)shadowView;
/**
* Called after view hierarchy manipulation has finished, and all shadow props
* have been set, but before layout has been performed. Useful for performing
* custom layout logic or tasks that involve walking the view hierarchy.
* To be deprecated, hopefully.
*/
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(NSDictionary<NSNumber *, RCTShadowView *> *)shadowViewRegistry;
/**
* This handles the simple case, where JS and native property names match.
*/

View File

@ -96,16 +96,6 @@ RCT_EXPORT_MODULE()
];
}
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(__unused RCTShadowView *)shadowView
{
return nil;
}
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(__unused NSDictionary<NSNumber *, RCTShadowView *> *)shadowViewRegistry
{
return nil;
}
#pragma mark - View properties
#if TARGET_OS_TV