From 2716f53220f947c690d5f627286aad51313256a0 Mon Sep 17 00:00:00 2001 From: Valentin Shergin Date: Tue, 23 Jan 2018 23:17:57 -0800 Subject: [PATCH] The New 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 and components. * All -specific APIs were removed from UIManager and co (it's about ~16 public methods which were used exclusively only by ). * 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 capabilities (e.g. rich text inside single line is now supported). Screenshots: https://cl.ly/2j3r1V0L0324 https://cl.ly/3N2V3C3d3q3R Reviewed By: mmmulani Differential Revision: D6617326 fbshipit-source-id: 35d4d81b35c9870e9557d0211c0e934e6072a41e --- .../Text/BaseText/RCTBaseTextShadowView.h | 26 + .../Text/BaseText/RCTBaseTextShadowView.m | 125 +++ .../RCTBaseTextViewManager.h} | 8 +- .../Text/BaseText/RCTBaseTextViewManager.m | 57 ++ Libraries/Text/RCTConvert+Text.h | 8 +- Libraries/Text/RCTFontAttributes.h | 31 - Libraries/Text/RCTFontAttributes.m | 112 --- .../Text/RCTText.xcodeproj/project.pbxproj | 578 +++++++----- Libraries/Text/RCTTextAttributes.h | 85 ++ Libraries/Text/RCTTextAttributes.m | 270 ++++++ .../Text/{ => RawText}/RCTRawTextShadowView.h | 6 +- .../Text/{ => RawText}/RCTRawTextShadowView.m | 30 +- .../{ => RawText}/RCTRawTextViewManager.h | 4 + .../{ => RawText}/RCTRawTextViewManager.m | 5 + .../Text/Text/NSTextStorage+FontScaling.h | 22 + .../Text/Text/NSTextStorage+FontScaling.m | 139 +++ Libraries/Text/Text/RCTTextShadowView.h | 45 +- Libraries/Text/Text/RCTTextShadowView.m | 878 ++++++------------ Libraries/Text/Text/RCTTextView.h | 11 +- Libraries/Text/Text/RCTTextView.m | 124 +-- Libraries/Text/Text/RCTTextViewManager.h | 4 +- Libraries/Text/Text/RCTTextViewManager.m | 145 +-- .../RCTMultilineTextInputShadowView.m | 19 - .../Multiline/RCTMultilineTextInputView.h | 24 +- .../Multiline/RCTMultilineTextInputView.m | 358 +------ .../RCTMultilineTextInputViewManager.h | 4 + .../RCTMultilineTextInputViewManager.m | 19 - .../Text/TextInput/Multiline/RCTUITextView.h | 2 +- .../Text/TextInput/Multiline/RCTUITextView.m | 6 +- .../TextInput/RCTBackedTextInputDelegate.h | 4 + .../RCTBackedTextInputDelegateAdapter.h | 8 +- .../RCTBackedTextInputDelegateAdapter.m | 78 +- .../RCTBackedTextInputViewProtocol.h | 14 +- .../TextInput/RCTBaseTextInputShadowView.h | 27 + .../TextInput/RCTBaseTextInputShadowView.m | 251 +++++ .../Text/TextInput/RCTBaseTextInputView.h | 44 +- .../Text/TextInput/RCTBaseTextInputView.m | 324 +++++-- .../TextInput/RCTBaseTextInputViewManager.h | 4 +- .../TextInput/RCTBaseTextInputViewManager.m | 89 +- .../Singleline/RCTSinglelineTextInputView.h | 12 +- .../Singleline/RCTSinglelineTextInputView.m | 113 +-- .../RCTSinglelineTextInputViewManager.h | 4 + .../RCTSinglelineTextInputViewManager.m | 22 +- .../RCTVirtualTextShadowView.h} | 4 +- .../VirtualText/RCTVirtualTextShadowView.m | 75 ++ .../RCTVirtualTextViewManager.h} | 4 +- .../RCTVirtualTextViewManager.m} | 17 +- RNTester/js/TextExample.ios.js | 33 +- RNTester/js/TextInputExample.ios.js | 108 +++ React/Modules/RCTAccessibilityManager.m | 3 +- React/Modules/RCTUIManager.m | 48 - React/Views/RCTComponentData.h | 2 - React/Views/RCTComponentData.m | 17 - React/Views/RCTShadowView.h | 12 +- React/Views/RCTShadowView.m | 91 +- React/Views/RCTViewManager.h | 14 - React/Views/RCTViewManager.m | 10 - 57 files changed, 2462 insertions(+), 2115 deletions(-) create mode 100644 Libraries/Text/BaseText/RCTBaseTextShadowView.h create mode 100644 Libraries/Text/BaseText/RCTBaseTextShadowView.m rename Libraries/Text/{RCTFontAttributesDelegate.h => BaseText/RCTBaseTextViewManager.h} (70%) create mode 100644 Libraries/Text/BaseText/RCTBaseTextViewManager.m delete mode 100644 Libraries/Text/RCTFontAttributes.h delete mode 100644 Libraries/Text/RCTFontAttributes.m create mode 100644 Libraries/Text/RCTTextAttributes.h create mode 100644 Libraries/Text/RCTTextAttributes.m rename Libraries/Text/{ => RawText}/RCTRawTextShadowView.h (79%) rename Libraries/Text/{ => RawText}/RCTRawTextShadowView.m (53%) rename Libraries/Text/{ => RawText}/RCTRawTextViewManager.h (89%) rename Libraries/Text/{ => RawText}/RCTRawTextViewManager.m (92%) create mode 100644 Libraries/Text/Text/NSTextStorage+FontScaling.h create mode 100644 Libraries/Text/Text/NSTextStorage+FontScaling.m delete mode 100644 Libraries/Text/TextInput/Multiline/RCTMultilineTextInputShadowView.m create mode 100644 Libraries/Text/TextInput/RCTBaseTextInputShadowView.h create mode 100644 Libraries/Text/TextInput/RCTBaseTextInputShadowView.m rename Libraries/Text/{TextInput/Multiline/RCTMultilineTextInputShadowView.h => VirtualText/RCTVirtualTextShadowView.h} (77%) create mode 100644 Libraries/Text/VirtualText/RCTVirtualTextShadowView.m rename Libraries/Text/{TextInput/Singleline/RCTSinglelineTextInputShadowView.h => VirtualText/RCTVirtualTextViewManager.h} (76%) rename Libraries/Text/{TextInput/Singleline/RCTSinglelineTextInputShadowView.m => VirtualText/RCTVirtualTextViewManager.m} (54%) diff --git a/Libraries/Text/BaseText/RCTBaseTextShadowView.h b/Libraries/Text/BaseText/RCTBaseTextShadowView.h new file mode 100644 index 000000000..736eed8ac --- /dev/null +++ b/Libraries/Text/BaseText/RCTBaseTextShadowView.h @@ -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 + +#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 diff --git a/Libraries/Text/BaseText/RCTBaseTextShadowView.m b/Libraries/Text/BaseText/RCTBaseTextShadowView.m new file mode 100644 index 000000000..efafad36d --- /dev/null +++ b/Libraries/Text/BaseText/RCTBaseTextShadowView.m @@ -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 + +#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 *)changedProps +{ + [super didSetProps:changedProps]; + [self dirtyLayout]; +} + +@end diff --git a/Libraries/Text/RCTFontAttributesDelegate.h b/Libraries/Text/BaseText/RCTBaseTextViewManager.h similarity index 70% rename from Libraries/Text/RCTFontAttributesDelegate.h rename to Libraries/Text/BaseText/RCTBaseTextViewManager.h index 6436c3c8c..da73cf070 100644 --- a/Libraries/Text/RCTFontAttributesDelegate.h +++ b/Libraries/Text/BaseText/RCTBaseTextViewManager.h @@ -7,8 +7,12 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -@protocol RCTFontAttributesDelegate +#import -- (void)fontAttributesDidChangeWithFont:(UIFont *)font; +NS_ASSUME_NONNULL_BEGIN + +@interface RCTBaseTextViewManager : RCTViewManager @end + +NS_ASSUME_NONNULL_END diff --git a/Libraries/Text/BaseText/RCTBaseTextViewManager.m b/Libraries/Text/BaseText/RCTBaseTextViewManager.m new file mode 100644 index 000000000..89640c7e9 --- /dev/null +++ b/Libraries/Text/BaseText/RCTBaseTextViewManager.m @@ -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 diff --git a/Libraries/Text/RCTConvert+Text.h b/Libraries/Text/RCTConvert+Text.h index e56508229..f48a794a2 100644 --- a/Libraries/Text/RCTConvert+Text.h +++ b/Libraries/Text/RCTConvert+Text.h @@ -9,9 +9,13 @@ #import +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 diff --git a/Libraries/Text/RCTFontAttributes.h b/Libraries/Text/RCTFontAttributes.h deleted file mode 100644 index 37b9954cd..000000000 --- a/Libraries/Text/RCTFontAttributes.h +++ /dev/null @@ -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 - -#import "RCTFontAttributesDelegate.h" - -@class RCTAccessibilityManager; - -@interface RCTFontAttributes : NSObject - -@property (nonatomic, weak) id 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 diff --git a/Libraries/Text/RCTFontAttributes.m b/Libraries/Text/RCTFontAttributes.m deleted file mode 100644 index 3cb8da119..000000000 --- a/Libraries/Text/RCTFontAttributes.m +++ /dev/null @@ -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 -#import -#import -#import - -@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 diff --git a/Libraries/Text/RCTText.xcodeproj/project.pbxproj b/Libraries/Text/RCTText.xcodeproj/project.pbxproj index 2a26851be..a79fd9423 100644 --- a/Libraries/Text/RCTText.xcodeproj/project.pbxproj +++ b/Libraries/Text/RCTText.xcodeproj/project.pbxproj @@ -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 = ""; }; - 59E6042C1FE9CAF100BD90C5 /* RCTRawTextShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRawTextShadowView.h; sourceTree = ""; }; - 59E6042E1FE9CAF100BD90C5 /* RCTTextShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextShadowView.h; sourceTree = ""; }; - 59E6042F1FE9CAF100BD90C5 /* RCTTextShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextShadowView.m; sourceTree = ""; }; - 59E604301FE9CAF100BD90C5 /* RCTTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextView.h; sourceTree = ""; }; - 59E604311FE9CAF100BD90C5 /* RCTTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextView.m; sourceTree = ""; }; - 59E604321FE9CAF100BD90C5 /* RCTTextViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextViewManager.h; sourceTree = ""; }; - 59E604331FE9CAF100BD90C5 /* RCTTextViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextViewManager.m; sourceTree = ""; }; - 59E604341FE9CAF100BD90C5 /* RCTRawTextShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRawTextShadowView.m; sourceTree = ""; }; - 59E604351FE9CAF100BD90C5 /* RCTRawTextViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRawTextViewManager.h; sourceTree = ""; }; - 59E604361FE9CAF100BD90C5 /* RCTRawTextViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRawTextViewManager.m; sourceTree = ""; }; - 59E604391FE9CAF100BD90C5 /* RCTMultilineTextInputShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMultilineTextInputShadowView.h; sourceTree = ""; }; - 59E6043A1FE9CAF100BD90C5 /* RCTMultilineTextInputShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMultilineTextInputShadowView.m; sourceTree = ""; }; - 59E6043B1FE9CAF100BD90C5 /* RCTMultilineTextInputView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMultilineTextInputView.h; sourceTree = ""; }; - 59E6043C1FE9CAF100BD90C5 /* RCTMultilineTextInputView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMultilineTextInputView.m; sourceTree = ""; }; - 59E6043D1FE9CAF100BD90C5 /* RCTMultilineTextInputViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMultilineTextInputViewManager.h; sourceTree = ""; }; - 59E6043E1FE9CAF100BD90C5 /* RCTMultilineTextInputViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMultilineTextInputViewManager.m; sourceTree = ""; }; - 59E6043F1FE9CAF100BD90C5 /* RCTUITextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTUITextView.h; sourceTree = ""; }; - 59E604401FE9CAF100BD90C5 /* RCTUITextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUITextView.m; sourceTree = ""; }; - 59E604411FE9CAF100BD90C5 /* RCTBackedTextInputDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBackedTextInputDelegate.h; sourceTree = ""; }; - 59E604421FE9CAF100BD90C5 /* RCTBackedTextInputDelegateAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBackedTextInputDelegateAdapter.h; sourceTree = ""; }; - 59E604431FE9CAF100BD90C5 /* RCTBackedTextInputDelegateAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBackedTextInputDelegateAdapter.m; sourceTree = ""; }; - 59E604441FE9CAF100BD90C5 /* RCTBackedTextInputViewProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBackedTextInputViewProtocol.h; sourceTree = ""; }; - 59E604451FE9CAF100BD90C5 /* RCTBaseTextInputView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBaseTextInputView.h; sourceTree = ""; }; - 59E604461FE9CAF100BD90C5 /* RCTBaseTextInputView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBaseTextInputView.m; sourceTree = ""; }; - 59E604471FE9CAF100BD90C5 /* RCTTextSelection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextSelection.h; sourceTree = ""; }; - 59E604481FE9CAF100BD90C5 /* RCTTextSelection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextSelection.m; sourceTree = ""; }; - 59E6044A1FE9CAF100BD90C5 /* RCTSinglelineTextInputShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSinglelineTextInputShadowView.h; sourceTree = ""; }; - 59E6044B1FE9CAF100BD90C5 /* RCTSinglelineTextInputShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSinglelineTextInputShadowView.m; sourceTree = ""; }; - 59E6044C1FE9CAF100BD90C5 /* RCTSinglelineTextInputView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSinglelineTextInputView.h; sourceTree = ""; }; - 59E6044D1FE9CAF100BD90C5 /* RCTSinglelineTextInputView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSinglelineTextInputView.m; sourceTree = ""; }; - 59E6044E1FE9CAF100BD90C5 /* RCTSinglelineTextInputViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSinglelineTextInputViewManager.h; sourceTree = ""; }; - 59E6044F1FE9CAF100BD90C5 /* RCTSinglelineTextInputViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSinglelineTextInputViewManager.m; sourceTree = ""; }; - 59E604501FE9CAF100BD90C5 /* RCTUITextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTUITextField.h; sourceTree = ""; }; - 59E604511FE9CAF100BD90C5 /* RCTUITextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUITextField.m; sourceTree = ""; }; - 657980E72008B2EE00E908AF /* RCTBaseTextInputViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBaseTextInputViewManager.m; sourceTree = ""; }; - A85C82981F742AA20036C019 /* RCTFontAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTFontAttributes.h; sourceTree = ""; }; - A85C82991F742AA20036C019 /* RCTFontAttributes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTFontAttributes.m; sourceTree = ""; }; - A85C82BA1F742D8F0036C019 /* RCTFontAttributesDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTFontAttributesDelegate.h; sourceTree = ""; }; - AF3225F71DE5574F00D3E7E7 /* RCTConvert+Text.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTConvert+Text.h"; sourceTree = ""; }; - AF3225F81DE5574F00D3E7E7 /* RCTConvert+Text.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+Text.m"; sourceTree = ""; }; + 5956B0F9200FEBA9008D9D16 /* RCTConvert+Text.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTConvert+Text.h"; sourceTree = ""; }; + 5956B0FB200FEBA9008D9D16 /* RCTRawTextViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRawTextViewManager.h; sourceTree = ""; }; + 5956B0FC200FEBA9008D9D16 /* RCTRawTextShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRawTextShadowView.h; sourceTree = ""; }; + 5956B0FD200FEBA9008D9D16 /* RCTRawTextShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRawTextShadowView.m; sourceTree = ""; }; + 5956B0FE200FEBA9008D9D16 /* RCTRawTextViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRawTextViewManager.m; sourceTree = ""; }; + 5956B101200FEBA9008D9D16 /* RCTSinglelineTextInputView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSinglelineTextInputView.m; sourceTree = ""; }; + 5956B102200FEBA9008D9D16 /* RCTSinglelineTextInputViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSinglelineTextInputViewManager.h; sourceTree = ""; }; + 5956B103200FEBA9008D9D16 /* RCTUITextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUITextField.m; sourceTree = ""; }; + 5956B104200FEBA9008D9D16 /* RCTSinglelineTextInputView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSinglelineTextInputView.h; sourceTree = ""; }; + 5956B105200FEBA9008D9D16 /* RCTUITextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTUITextField.h; sourceTree = ""; }; + 5956B106200FEBA9008D9D16 /* RCTSinglelineTextInputViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSinglelineTextInputViewManager.m; sourceTree = ""; }; + 5956B107200FEBA9008D9D16 /* RCTBackedTextInputDelegateAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBackedTextInputDelegateAdapter.h; sourceTree = ""; }; + 5956B108200FEBA9008D9D16 /* RCTTextSelection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextSelection.h; sourceTree = ""; }; + 5956B109200FEBA9008D9D16 /* RCTBaseTextInputShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBaseTextInputShadowView.h; sourceTree = ""; }; + 5956B10A200FEBA9008D9D16 /* RCTBaseTextInputView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBaseTextInputView.m; sourceTree = ""; }; + 5956B10B200FEBA9008D9D16 /* RCTBaseTextInputViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBaseTextInputViewManager.h; sourceTree = ""; }; + 5956B10C200FEBA9008D9D16 /* RCTBackedTextInputDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBackedTextInputDelegate.h; sourceTree = ""; }; + 5956B10D200FEBA9008D9D16 /* RCTBackedTextInputDelegateAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBackedTextInputDelegateAdapter.m; sourceTree = ""; }; + 5956B10E200FEBA9008D9D16 /* RCTBaseTextInputView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBaseTextInputView.h; sourceTree = ""; }; + 5956B10F200FEBA9008D9D16 /* RCTBaseTextInputShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBaseTextInputShadowView.m; sourceTree = ""; }; + 5956B110200FEBA9008D9D16 /* RCTTextSelection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextSelection.m; sourceTree = ""; }; + 5956B111200FEBA9008D9D16 /* RCTBaseTextInputViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBaseTextInputViewManager.m; sourceTree = ""; }; + 5956B112200FEBA9008D9D16 /* RCTBackedTextInputViewProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBackedTextInputViewProtocol.h; sourceTree = ""; }; + 5956B114200FEBA9008D9D16 /* RCTMultilineTextInputViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMultilineTextInputViewManager.h; sourceTree = ""; }; + 5956B115200FEBA9008D9D16 /* RCTMultilineTextInputView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMultilineTextInputView.m; sourceTree = ""; }; + 5956B116200FEBA9008D9D16 /* RCTUITextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTUITextView.h; sourceTree = ""; }; + 5956B117200FEBA9008D9D16 /* RCTMultilineTextInputView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMultilineTextInputView.h; sourceTree = ""; }; + 5956B118200FEBA9008D9D16 /* RCTMultilineTextInputViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMultilineTextInputViewManager.m; sourceTree = ""; }; + 5956B119200FEBA9008D9D16 /* RCTUITextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUITextView.m; sourceTree = ""; }; + 5956B11A200FEBA9008D9D16 /* RCTTextAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextAttributes.h; sourceTree = ""; }; + 5956B11C200FEBA9008D9D16 /* RCTBaseTextViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBaseTextViewManager.h; sourceTree = ""; }; + 5956B11D200FEBA9008D9D16 /* RCTBaseTextShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBaseTextShadowView.h; sourceTree = ""; }; + 5956B11E200FEBA9008D9D16 /* RCTBaseTextShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBaseTextShadowView.m; sourceTree = ""; }; + 5956B11F200FEBA9008D9D16 /* RCTBaseTextViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBaseTextViewManager.m; sourceTree = ""; }; + 5956B120200FEBA9008D9D16 /* RCTTextAttributes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextAttributes.m; sourceTree = ""; }; + 5956B122200FEBAA008D9D16 /* RCTTextShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextShadowView.m; sourceTree = ""; }; + 5956B123200FEBAA008D9D16 /* RCTTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextView.h; sourceTree = ""; }; + 5956B124200FEBAA008D9D16 /* RCTTextViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextViewManager.h; sourceTree = ""; }; + 5956B125200FEBAA008D9D16 /* NSTextStorage+FontScaling.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSTextStorage+FontScaling.m"; sourceTree = ""; }; + 5956B126200FEBAA008D9D16 /* RCTTextShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextShadowView.h; sourceTree = ""; }; + 5956B127200FEBAA008D9D16 /* RCTTextViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextViewManager.m; sourceTree = ""; }; + 5956B128200FEBAA008D9D16 /* RCTTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextView.m; sourceTree = ""; }; + 5956B129200FEBAA008D9D16 /* NSTextStorage+FontScaling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSTextStorage+FontScaling.h"; sourceTree = ""; }; + 5956B12B200FEBAA008D9D16 /* RCTVirtualTextViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTVirtualTextViewManager.m; sourceTree = ""; }; + 5956B12C200FEBAA008D9D16 /* RCTVirtualTextShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTVirtualTextShadowView.h; sourceTree = ""; }; + 5956B12D200FEBAA008D9D16 /* RCTVirtualTextViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTVirtualTextViewManager.h; sourceTree = ""; }; + 5956B12E200FEBAA008D9D16 /* RCTVirtualTextShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTVirtualTextShadowView.m; sourceTree = ""; }; + 5956B12F200FEBAA008D9D16 /* RCTConvert+Text.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+Text.m"; sourceTree = ""; }; /* 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 = ""; @@ -235,66 +260,99 @@ name = Products; sourceTree = ""; }; - 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 = ""; }; - 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 = ""; }; - 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 = ""; + }; + 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 = ""; }; - 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 = ""; + }; + 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 = ""; + }; + 5956B12A200FEBAA008D9D16 /* VirtualText */ = { + isa = PBXGroup; + children = ( + 5956B12C200FEBAA008D9D16 /* RCTVirtualTextShadowView.h */, + 5956B12E200FEBAA008D9D16 /* RCTVirtualTextShadowView.m */, + 5956B12D200FEBAA008D9D16 /* RCTVirtualTextViewManager.h */, + 5956B12B200FEBAA008D9D16 /* RCTVirtualTextViewManager.m */, + ); + path = VirtualText; sourceTree = ""; }; /* 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; }; diff --git a/Libraries/Text/RCTTextAttributes.h b/Libraries/Text/RCTTextAttributes.h new file mode 100644 index 000000000..7752c2c66 --- /dev/null +++ b/Libraries/Text/RCTTextAttributes.h @@ -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 + +#import + +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 , , + * and . + */ +@interface RCTTextAttributes : NSObject + +// 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 *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 *)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 diff --git a/Libraries/Text/RCTTextAttributes.m b/Libraries/Text/RCTTextAttributes.m new file mode 100644 index 000000000..26d63bdda --- /dev/null +++ b/Libraries/Text/RCTTextAttributes.m @@ -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 +#import +#import + +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 *)effectiveTextAttributes +{ + NSMutableDictionary *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 diff --git a/Libraries/Text/RCTRawTextShadowView.h b/Libraries/Text/RawText/RCTRawTextShadowView.h similarity index 79% rename from Libraries/Text/RCTRawTextShadowView.h rename to Libraries/Text/RawText/RCTRawTextShadowView.h index be896ff78..52e61f0ba 100644 --- a/Libraries/Text/RCTRawTextShadowView.h +++ b/Libraries/Text/RawText/RCTRawTextShadowView.h @@ -9,8 +9,12 @@ #import +NS_ASSUME_NONNULL_BEGIN + @interface RCTRawTextShadowView : RCTShadowView -@property (nonatomic, copy) NSString *text; +@property (nonatomic, copy, nullable) NSString *text; @end + +NS_ASSUME_NONNULL_END diff --git a/Libraries/Text/RCTRawTextShadowView.m b/Libraries/Text/RawText/RCTRawTextShadowView.m similarity index 53% rename from Libraries/Text/RCTRawTextShadowView.m rename to Libraries/Text/RawText/RCTRawTextShadowView.m index 17f8b4163..d350d2968 100644 --- a/Libraries/Text/RCTRawTextShadowView.m +++ b/Libraries/Text/RawText/RCTRawTextShadowView.m @@ -9,39 +9,23 @@ #import "RCTRawTextShadowView.h" -#import +#import @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; diff --git a/Libraries/Text/RCTRawTextViewManager.h b/Libraries/Text/RawText/RCTRawTextViewManager.h similarity index 89% rename from Libraries/Text/RCTRawTextViewManager.h rename to Libraries/Text/RawText/RCTRawTextViewManager.h index cd376005e..38d83e385 100644 --- a/Libraries/Text/RCTRawTextViewManager.h +++ b/Libraries/Text/RawText/RCTRawTextViewManager.h @@ -9,6 +9,10 @@ #import +NS_ASSUME_NONNULL_BEGIN + @interface RCTRawTextViewManager : RCTViewManager @end + +NS_ASSUME_NONNULL_END diff --git a/Libraries/Text/RCTRawTextViewManager.m b/Libraries/Text/RawText/RCTRawTextViewManager.m similarity index 92% rename from Libraries/Text/RCTRawTextViewManager.m rename to Libraries/Text/RawText/RCTRawTextViewManager.m index 8eccfd5c6..17233cdc7 100644 --- a/Libraries/Text/RCTRawTextViewManager.m +++ b/Libraries/Text/RawText/RCTRawTextViewManager.m @@ -15,6 +15,11 @@ RCT_EXPORT_MODULE(RCTRawText) +- (UIView *)view +{ + return [UIView new]; +} + - (RCTShadowView *)shadowView { return [RCTRawTextShadowView new]; diff --git a/Libraries/Text/Text/NSTextStorage+FontScaling.h b/Libraries/Text/Text/NSTextStorage+FontScaling.h new file mode 100644 index 000000000..f3ea09ab2 --- /dev/null +++ b/Libraries/Text/Text/NSTextStorage+FontScaling.h @@ -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 + +@interface NSTextStorage (FontScaling) + +- (void)scaleFontSizeToFitSize:(CGSize)size + minimumFontSize:(CGFloat)minimumFontSize + maximumFontSize:(CGFloat)maximumFontSize; + +- (void)scaleFontSizeWithRatio:(CGFloat)ratio + minimumFontSize:(CGFloat)minimumFontSize + maximumFontSize:(CGFloat)maximumFontSize; + +@end diff --git a/Libraries/Text/Text/NSTextStorage+FontScaling.m b/Libraries/Text/Text/NSTextStorage+FontScaling.m new file mode 100644 index 000000000..4233bef96 --- /dev/null +++ b/Libraries/Text/Text/NSTextStorage+FontScaling.m @@ -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 diff --git a/Libraries/Text/Text/RCTTextShadowView.h b/Libraries/Text/Text/RCTTextShadowView.h index 920f68bf7..6cdd8fdd3 100644 --- a/Libraries/Text/Text/RCTTextShadowView.h +++ b/Libraries/Text/Text/RCTTextShadowView.h @@ -8,49 +8,22 @@ */ #import -#import -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 diff --git a/Libraries/Text/Text/RCTTextShadowView.m b/Libraries/Text/Text/RCTTextShadowView.m index db9ef0423..4f29f057b 100644 --- a/Libraries/Text/Text/RCTTextShadowView.m +++ b/Libraries/Text/Text/RCTTextShadowView.m @@ -9,678 +9,336 @@ #import "RCTTextShadowView.h" -#import #import -#import -#import -#import #import #import -#import +#import -#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 *_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 *)processUpdatedProperties:(NSMutableSet *)applierBlocks - parentProperties:(NSDictionary *)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 *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 - * `` 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 *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 *viewRegistry) { + RCTTextView *textView = (RCTTextView *)viewRegistry[tag]; + if (!textView) { + return; } - }]; - return parentProperties; + NSMutableArray *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 *)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 *)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 *)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 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 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 diff --git a/Libraries/Text/Text/RCTTextView.h b/Libraries/Text/Text/RCTTextView.h index b1843cad2..c8ba22adc 100644 --- a/Libraries/Text/Text/RCTTextView.h +++ b/Libraries/Text/Text/RCTTextView.h @@ -9,11 +9,16 @@ #import +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 *)descendantViews; + @end + +NS_ASSUME_NONNULL_END diff --git a/Libraries/Text/Text/RCTTextView.m b/Libraries/Text/Text/RCTTextView.m index 20554e17d..d065e8073 100644 --- a/Libraries/Text/Text/RCTTextView.m +++ b/Libraries/Text/Text/RCTTextView.m @@ -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 *_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 *)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]; diff --git a/Libraries/Text/Text/RCTTextViewManager.h b/Libraries/Text/Text/RCTTextViewManager.h index 9028878bd..957c88d47 100644 --- a/Libraries/Text/Text/RCTTextViewManager.h +++ b/Libraries/Text/Text/RCTTextViewManager.h @@ -9,6 +9,8 @@ #import -@interface RCTTextViewManager : RCTViewManager +#import "RCTBaseTextViewManager.h" + +@interface RCTTextViewManager : RCTBaseTextViewManager @end diff --git a/Libraries/Text/Text/RCTTextViewManager.m b/Libraries/Text/Text/RCTTextViewManager.m index 83dcedfb7..58b559de1 100644 --- a/Libraries/Text/Text/RCTTextViewManager.m +++ b/Libraries/Text/Text/RCTTextViewManager.m @@ -10,41 +10,51 @@ #import "RCTTextViewManager.h" #import -#import -#import -#import #import -#import -#import +#import +#import +#import -#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 () @end - @implementation RCTTextViewManager +{ + NSHashTable *_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 *)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 *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 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 *viewRegistry) { - RCTTextView *text = viewRegistry[reactTag]; - text.contentInset = padding; - }; + [self.bridge.uiManager setNeedsLayout]; } @end diff --git a/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputShadowView.m b/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputShadowView.m deleted file mode 100644 index 875f9bb1c..000000000 --- a/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputShadowView.m +++ /dev/null @@ -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 diff --git a/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.h b/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.h index 8a55a7a7c..7d2802359 100644 --- a/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.h +++ b/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.h @@ -7,30 +7,12 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import - -#import -#import - #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 diff --git a/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.m b/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.m index dea7d888c..d97c95172 100644 --- a/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.m +++ b/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.m @@ -9,382 +9,60 @@ #import "RCTMultilineTextInputView.h" -#import -#import -#import -#import #import -#import -#import "RCTTextShadowView.h" -#import "RCTTextView.h" -#import "RCTTextSelection.h" #import "RCTUITextView.h" -@interface RCTMultilineTextInputView () - -@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 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)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 into - there can only be one."); - } - _richTextView = (RCTTextView *)subview; - - // If this is in rich text editing mode, and the child node providing rich text - // styling has a backgroundColor, then the attributedText produced by the child 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 *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 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, ¤tRange, &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) diff --git a/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.h b/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.h index 322cfa167..b2053435f 100644 --- a/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.h +++ b/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.h @@ -9,6 +9,10 @@ #import "RCTBaseTextInputViewManager.h" +NS_ASSUME_NONNULL_BEGIN + @interface RCTMultilineTextInputViewManager : RCTBaseTextInputViewManager @end + +NS_ASSUME_NONNULL_END diff --git a/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.m b/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.m index 9676a63f2..de8495573 100644 --- a/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.m +++ b/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.m @@ -9,25 +9,12 @@ #import "RCTMultilineTextInputViewManager.h" -#import -#import -#import -#import -#import - -#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 (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 diff --git a/Libraries/Text/TextInput/Multiline/RCTUITextView.h b/Libraries/Text/TextInput/Multiline/RCTUITextView.h index 47b81c32c..104e10590 100644 --- a/Libraries/Text/TextInput/Multiline/RCTUITextView.h +++ b/Libraries/Text/TextInput/Multiline/RCTUITextView.h @@ -21,7 +21,7 @@ NS_ASSUME_NONNULL_BEGIN @interface RCTUITextView : UITextView - (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 textInputDelegate; diff --git a/Libraries/Text/TextInput/Multiline/RCTUITextView.m b/Libraries/Text/TextInput/Multiline/RCTUITextView.m index 45d3cf6df..3462b833f 100644 --- a/Libraries/Text/TextInput/Multiline/RCTUITextView.m +++ b/Libraries/Text/TextInput/Multiline/RCTUITextView.m @@ -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; } diff --git a/Libraries/Text/TextInput/RCTBackedTextInputDelegate.h b/Libraries/Text/TextInput/RCTBackedTextInputDelegate.h index d1d40c67a..d124ef2ec 100644 --- a/Libraries/Text/TextInput/RCTBackedTextInputDelegate.h +++ b/Libraries/Text/TextInput/RCTBackedTextInputDelegate.h @@ -11,6 +11,8 @@ @protocol RCTBackedTextInputViewProtocol; +NS_ASSUME_NONNULL_BEGIN + @protocol RCTBackedTextInputDelegate - (BOOL)textInputShouldBeginEditing; // Return `NO` to disallow editing. @@ -28,3 +30,5 @@ - (void)textInputDidChangeSelection; @end + +NS_ASSUME_NONNULL_END diff --git a/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.h b/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.h index dce127bbb..b9914c235 100644 --- a/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.h +++ b/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.h @@ -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 *)backedTextInput; +- (instancetype)initWithTextField:(UITextField *)backedTextInputView; - (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)textRange; - (void)selectedTextRangeWasSet; @@ -27,8 +29,10 @@ @interface RCTBackedTextViewDelegateAdapter : NSObject -- (instancetype)initWithTextView:(UITextView *)backedTextInput; +- (instancetype)initWithTextView:(UITextView *)backedTextInputView; - (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)textRange; @end + +NS_ASSUME_NONNULL_END diff --git a/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.m b/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.m index f902f4fd4..856d708f6 100644 --- a/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.m +++ b/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.m @@ -17,19 +17,19 @@ static void *TextFieldSelectionObservingContext = &TextFieldSelectionObservingCo @end @implementation RCTBackedTextFieldDelegateAdapter { - __weak UITextField *_backedTextInput; + __weak UITextField *_backedTextInputView; BOOL _textDidChangeIsComing; UITextRange *_previousSelectedTextRange; } -- (instancetype)initWithTextField:(UITextField *)backedTextInput +- (instancetype)initWithTextField:(UITextField *)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 *_backedTextInput; + __weak UITextView *_backedTextInputView; BOOL _textDidChangeIsComing; UITextRange *_previousSelectedTextRange; } -- (instancetype)initWithTextView:(UITextView *)backedTextInput +- (instancetype)initWithTextView:(UITextView *)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 diff --git a/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h b/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h index 90e0aa4d6..32a2a6ae3 100644 --- a/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h +++ b/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h @@ -11,14 +11,17 @@ @protocol RCTBackedTextInputDelegate; +NS_ASSUME_NONNULL_BEGIN + @protocol RCTBackedTextInputViewProtocol -@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 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 diff --git a/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h b/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h new file mode 100644 index 000000000..3f1e8185b --- /dev/null +++ b/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h @@ -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 diff --git a/Libraries/Text/TextInput/RCTBaseTextInputShadowView.m b/Libraries/Text/TextInput/RCTBaseTextInputShadowView.m new file mode 100644 index 000000000..e145d4a3b --- /dev/null +++ b/Libraries/Text/TextInput/RCTBaseTextInputShadowView.m @@ -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 +#import +#import +#import + +#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 *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 diff --git a/Libraries/Text/TextInput/RCTBaseTextInputView.h b/Libraries/Text/TextInput/RCTBaseTextInputView.h index 3176c6e30..56c8d2b94 100644 --- a/Libraries/Text/TextInput/RCTBaseTextInputView.h +++ b/Libraries/Text/TextInput/RCTBaseTextInputView.h @@ -11,22 +11,17 @@ #import +#import "RCTBackedTextInputDelegate.h" #import "RCTBackedTextInputViewProtocol.h" -#import "RCTFontAttributes.h" -#import "RCTFontAttributesDelegate.h" @class RCTBridge; @class RCTEventDispatcher; +@class RCTTextAttributes; @class RCTTextSelection; -@interface RCTBaseTextInputView : RCTView { -@protected - __weak RCTBridge *_bridge; - RCTEventDispatcher *_eventDispatcher; - NSInteger _nativeEventCount; - NSInteger _mostRecentEventCount; - BOOL _blurOnSubmit; -} +NS_ASSUME_NONNULL_BEGIN + +@interface RCTBaseTextInputView : RCTView - (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; @@ -36,33 +31,24 @@ @property (nonatomic, readonly) UIView *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 diff --git a/Libraries/Text/TextInput/RCTBaseTextInputView.m b/Libraries/Text/TextInput/RCTBaseTextInputView.m index 855f3b7ec..91f6a980f 100644 --- a/Libraries/Text/TextInput/RCTBaseTextInputView.m +++ b/Libraries/Text/TextInput/RCTBaseTextInputView.m @@ -17,11 +17,15 @@ #import #import +#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)backedTextInputView +- (UIView *)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 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 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 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 backedTextInput = self.backedTextInputView; + id 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 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 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, ¤tRange, &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 diff --git a/Libraries/Text/TextInput/RCTBaseTextInputViewManager.h b/Libraries/Text/TextInput/RCTBaseTextInputViewManager.h index 0d5b669c9..710536aaa 100644 --- a/Libraries/Text/TextInput/RCTBaseTextInputViewManager.h +++ b/Libraries/Text/TextInput/RCTBaseTextInputViewManager.h @@ -7,8 +7,8 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import +#import "RCTBaseTextViewManager.h" -@interface RCTBaseTextInputViewManager : RCTViewManager +@interface RCTBaseTextInputViewManager : RCTBaseTextViewManager @end diff --git a/Libraries/Text/TextInput/RCTBaseTextInputViewManager.m b/Libraries/Text/TextInput/RCTBaseTextInputViewManager.m index c2ffffe21..7e98eda2d 100644 --- a/Libraries/Text/TextInput/RCTBaseTextInputViewManager.m +++ b/Libraries/Text/TextInput/RCTBaseTextInputViewManager.m @@ -9,31 +9,36 @@ #import "RCTBaseTextInputViewManager.h" +#import #import #import #import #import #import +#import +#import -#import "RCTConvert+Text.h" +#import "RCTBaseTextInputShadowView.h" #import "RCTBaseTextInputView.h" +#import "RCTConvert+Text.h" + +@interface RCTBaseTextInputViewManager () + +@end @implementation RCTBaseTextInputViewManager +{ + NSHashTable *_shadowViews; +} RCT_EXPORT_MODULE() #pragma mark - Unified 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 *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 diff --git a/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputView.h b/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputView.h index 5054ffbfc..5338e0c63 100644 --- a/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputView.h +++ b/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputView.h @@ -7,18 +7,12 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import - -#import -#import - #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 diff --git a/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputView.m b/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputView.m index 1c51fe070..4f4959b63 100644 --- a/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputView.m +++ b/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputView.m @@ -10,133 +10,36 @@ #import "RCTSinglelineTextInputView.h" #import -#import -#import -#import -#import -#import -#import -#import "RCTBackedTextInputDelegate.h" -#import "RCTTextSelection.h" #import "RCTUITextField.h" -@interface RCTSinglelineTextInputView () - -@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 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)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 diff --git a/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.h b/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.h index 90f5fbda2..57439e6bf 100644 --- a/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.h +++ b/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.h @@ -9,6 +9,10 @@ #import "RCTBaseTextInputViewManager.h" +NS_ASSUME_NONNULL_BEGIN + @interface RCTSinglelineTextInputViewManager : RCTBaseTextInputViewManager @end + +NS_ASSUME_NONNULL_END diff --git a/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.m b/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.m index 5b96501aa..6a74da53c 100644 --- a/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.m +++ b/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.m @@ -9,15 +9,8 @@ #import "RCTSinglelineTextInputViewManager.h" -#import -#import -#import -#import - -#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 (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 diff --git a/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputShadowView.h b/Libraries/Text/VirtualText/RCTVirtualTextShadowView.h similarity index 77% rename from Libraries/Text/TextInput/Multiline/RCTMultilineTextInputShadowView.h rename to Libraries/Text/VirtualText/RCTVirtualTextShadowView.h index 27198d9db..ae5d158b0 100644 --- a/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputShadowView.h +++ b/Libraries/Text/VirtualText/RCTVirtualTextShadowView.h @@ -7,8 +7,8 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import +#import "RCTBaseTextShadowView.h" -@interface RCTMultilineTextInputShadowView : RCTShadowView +@interface RCTVirtualTextShadowView : RCTBaseTextShadowView @end diff --git a/Libraries/Text/VirtualText/RCTVirtualTextShadowView.m b/Libraries/Text/VirtualText/RCTVirtualTextShadowView.m new file mode 100644 index 000000000..b5710f13c --- /dev/null +++ b/Libraries/Text/VirtualText/RCTVirtualTextShadowView.m @@ -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 +#import + +#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 diff --git a/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputShadowView.h b/Libraries/Text/VirtualText/RCTVirtualTextViewManager.h similarity index 76% rename from Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputShadowView.h rename to Libraries/Text/VirtualText/RCTVirtualTextViewManager.h index bcea71eee..b7225dbb5 100644 --- a/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputShadowView.h +++ b/Libraries/Text/VirtualText/RCTVirtualTextViewManager.h @@ -7,8 +7,8 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import +#import "RCTBaseTextViewManager.h" -@interface RCTSinglelineTextInputShadowView : RCTShadowView +@interface RCTVirtualTextViewManager : RCTBaseTextViewManager @end diff --git a/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputShadowView.m b/Libraries/Text/VirtualText/RCTVirtualTextViewManager.m similarity index 54% rename from Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputShadowView.m rename to Libraries/Text/VirtualText/RCTVirtualTextViewManager.m index fc50fc9bb..64cf16172 100644 --- a/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputShadowView.m +++ b/Libraries/Text/VirtualText/RCTVirtualTextViewManager.m @@ -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 diff --git a/RNTester/js/TextExample.ios.js b/RNTester/js/TextExample.ios.js index b4e2a8482..5caf00892 100644 --- a/RNTester/js/TextExample.ios.js +++ b/RNTester/js/TextExample.ios.js @@ -233,20 +233,8 @@ exports.examples = [ render: function() { return ( - The text - - Text Inside - Another text Inside - - Total inseption - - Insepted Text Inside - - - The text should wrap if it goes on multiple lines. See, this is going to the next line. - The text after. ); }, @@ -759,6 +747,27 @@ exports.examples = [ ); }, }, + { + title: 'Nested content', + render: function() { + return ( + + This text has a view + + which has + another text inside. + + And moreover, it has another view + + with another text inside! + + + + Because we need to go deeper. + + ); + }, + }, { title: 'Dynamic Font Size Adjustment', render: function(): React.Element { diff --git a/RNTester/js/TextInputExample.ios.js b/RNTester/js/TextInputExample.ios.js index efb116533..8266f067b 100644 --- a/RNTester/js/TextInputExample.ios.js +++ b/RNTester/js/TextInputExample.ios.js @@ -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 ( + + Width: + this.setState({width: value})} + /> + Multiline: + this.setState({multiline: value})} + /> + TextInput: + this.setState({text: value})} + onContentSizeChange={(event) => this.setState({contentSize: event.nativeEvent.contentSize})} + {...props} + /> + Plain text value representation: + {this.state.text} + Content Size: {JSON.stringify(this.state.contentSize)} + + ); + } +} + var styles = StyleSheet.create({ page: { paddingBottom: 300, @@ -478,6 +535,29 @@ exports.examples = [ ); } }, + { + title: 'Nested content and `value` property', + render: function() { + return ( + + + + (first raw text node) + (internal raw text node) + (last raw text node) + + + + + (first raw text node) + (internal raw text node) + (last raw text node) + + + + ); + } + }, { title: 'Keyboard types', render: function() { @@ -854,6 +934,34 @@ exports.examples = [ ); } }, + { + title: 'Auto-expanding', + render: function() { + return ( + + + + huge + + generic generic generic + + small small small small small small + + regular regular + + huge huge huge huge huge + + generic generic generic + + + ); + } + }, { title: 'Attributed text', render: function() { diff --git a/React/Modules/RCTAccessibilityManager.m b/React/Modules/RCTAccessibilityManager.m index 409e9792f..6434becff 100644 --- a/React/Modules/RCTAccessibilityManager.m +++ b/React/Modules/RCTAccessibilityManager.m @@ -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 diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 4b2dd504f..5ac1fa985 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -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 *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 *applierBlocks = [NSMutableSet setWithCapacity:1]; - [topView collectUpdatedProperties:applierBlocks parentProperties:@{}]; - - if (applierBlocks.count) { - [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *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:^{ diff --git a/React/Views/RCTComponentData.h b/React/Views/RCTComponentData.h index cf4d44e0b..633fd7139 100644 --- a/React/Views/RCTComponentData.h +++ b/React/Views/RCTComponentData.h @@ -33,6 +33,4 @@ - (NSDictionary *)viewConfig; -- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(NSDictionary *)registry; - @end diff --git a/React/Views/RCTComponentData.m b/React/Views/RCTComponentData.m index e094b1b7b..976d9fb20 100644 --- a/React/Views/RCTComponentData.m +++ b/React/Views/RCTComponentData.m @@ -37,7 +37,6 @@ static SEL selectorForType(NSString *type) id _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 *)registry -{ - if (_implementsUIBlockToAmendWithShadowViewRegistry) { - return [[self manager] uiBlockToAmendWithShadowViewRegistry:registry]; - } - return nil; -} - static NSString *moduleNameForClass(Class managerClass) { // Hackety hack, this partially re-implements RCTBridgeModuleNameForClass diff --git a/React/Views/RCTShadowView.h b/React/Views/RCTShadowView.h index 4020d3b0d..f9680e7ac 100644 --- a/React/Views/RCTShadowView.h +++ b/React/Views/RCTShadowView.h @@ -226,6 +226,11 @@ typedef void (^RCTApplierBlock)(NSDictionary *viewRegistry viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame absolutePosition:(CGPoint)absolutePosition NS_REQUIRES_SUPER; +- (void)applyLayoutWithFrame:(CGRect)frame + layoutDirection:(UIUserInterfaceLayoutDirection)layoutDirection + viewsWithUpdatedLayout:(NSMutableSet *)viewsWithUpdatedLayout + absolutePosition:(CGPoint)absolutePosition; + /** * Enumerate the child nodes and tell them to apply layout. */ @@ -253,13 +258,6 @@ typedef void (^RCTApplierBlock)(NSDictionary *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. */ diff --git a/React/Views/RCTShadowView.m b/React/Views/RCTShadowView.m index 23c619ff7..e0a2c7706 100644 --- a/React/Views/RCTShadowView.m +++ b/React/Views/RCTShadowView.m @@ -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 *)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 { diff --git a/React/Views/RCTViewManager.h b/React/Views/RCTViewManager.h index f78a6e0ba..4495a24cc 100644 --- a/React/Views/RCTViewManager.h +++ b/React/Views/RCTViewManager.h @@ -63,20 +63,6 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, NSDictionary *)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 *)shadowViewRegistry; - /** * This handles the simple case, where JS and native property names match. */ diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m index 54ad89d2d..56415d622 100644 --- a/React/Views/RCTViewManager.m +++ b/React/Views/RCTViewManager.m @@ -96,16 +96,6 @@ RCT_EXPORT_MODULE() ]; } -- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(__unused RCTShadowView *)shadowView -{ - return nil; -} - -- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(__unused NSDictionary *)shadowViewRegistry -{ - return nil; -} - #pragma mark - View properties #if TARGET_OS_TV