Skip to content

Commit

Permalink
The New <Text> on iOS
Browse files Browse the repository at this point in the history
Summary:
This is a complete rewrite of RCTText, the part of React Native which manages Text and TextInput components.

Key points:

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

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

Reviewed By: mmmulani

Differential Revision: D6617326

fbshipit-source-id: 35d4d81b35c9870e9557d0211c0e934e6072a41e
  • Loading branch information
shergin authored and facebook-github-bot committed Jan 24, 2018
1 parent cd263a2 commit 2716f53
Show file tree
Hide file tree
Showing 57 changed files with 2,425 additions and 2,078 deletions.
26 changes: 26 additions & 0 deletions Libraries/Text/BaseText/RCTBaseTextShadowView.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import <React/RCTShadowView.h>

#import "RCTTextAttributes.h"

NS_ASSUME_NONNULL_BEGIN

extern NSString *const RCTBaseTextShadowViewEmbeddedShadowViewAttributeName;

@interface RCTBaseTextShadowView : RCTShadowView

@property (nonatomic, strong) RCTTextAttributes *textAttributes;

- (NSAttributedString *)attributedTextWithBaseTextAttributes:(nullable RCTTextAttributes *)baseTextAttributes;

@end

NS_ASSUME_NONNULL_END
125 changes: 125 additions & 0 deletions Libraries/Text/BaseText/RCTBaseTextShadowView.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import "RCTBaseTextShadowView.h"

#import <React/RCTShadowView+Layout.h>

#import "RCTRawTextShadowView.h"
#import "RCTVirtualTextShadowView.h"

NSString *const RCTBaseTextShadowViewEmbeddedShadowViewAttributeName = @"RCTBaseTextShadowViewEmbeddedShadowViewAttributeName";

@implementation RCTBaseTextShadowView
{
NSAttributedString *_Nullable _cachedAttributedText;
RCTTextAttributes *_Nullable _cachedTextAttributes;
}

- (instancetype)init
{
if (self = [super init]) {
_textAttributes = [RCTTextAttributes new];
}

return self;
}

- (void)setReactTag:(NSNumber *)reactTag
{
[super setReactTag:reactTag];
_textAttributes.tag = reactTag;
}

#pragma mark - attributedString

- (NSAttributedString *)attributedTextWithBaseTextAttributes:(nullable RCTTextAttributes *)baseTextAttributes
{
RCTTextAttributes *textAttributes;

if (baseTextAttributes) {
textAttributes = [baseTextAttributes copy];
[textAttributes applyTextAttributes:self.textAttributes];
} else {
textAttributes = [self.textAttributes copy];
}

if (_cachedAttributedText && [_cachedTextAttributes isEqual:textAttributes]) {
return _cachedAttributedText;
}

NSMutableAttributedString *attributedText = [NSMutableAttributedString new];

[attributedText beginEditing];

for (RCTShadowView *shadowView in self.reactSubviews) {
// Special Case: RCTRawTextShadowView
if ([shadowView isKindOfClass:[RCTRawTextShadowView class]]) {
RCTRawTextShadowView *rawTextShadowView = (RCTRawTextShadowView *)shadowView;
NSString *text = rawTextShadowView.text;
if (text) {
NSAttributedString *rawTextAttributedString =
[[NSAttributedString alloc] initWithString:rawTextShadowView.text
attributes:textAttributes.effectiveTextAttributes];
[attributedText appendAttributedString:rawTextAttributedString];
}
continue;
}

// Special Case: RCTBaseTextShadowView
if ([shadowView isKindOfClass:[RCTBaseTextShadowView class]]) {
RCTBaseTextShadowView *baseTextShadowView = (RCTBaseTextShadowView *)shadowView;
NSAttributedString *baseTextAttributedString =
[baseTextShadowView attributedTextWithBaseTextAttributes:textAttributes];
[attributedText appendAttributedString:baseTextAttributedString];
continue;
}

// Generic Case: Any RCTShadowView
NSTextAttachment *attachment = [NSTextAttachment new];
NSMutableAttributedString *embeddedShadowViewAttributedString = [NSMutableAttributedString new];
[embeddedShadowViewAttributedString beginEditing];
[embeddedShadowViewAttributedString appendAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]];
[embeddedShadowViewAttributedString addAttribute:RCTBaseTextShadowViewEmbeddedShadowViewAttributeName
value:shadowView
range:(NSRange){0, embeddedShadowViewAttributedString.length}];
[embeddedShadowViewAttributedString endEditing];
[attributedText appendAttributedString:embeddedShadowViewAttributedString];
}

[attributedText endEditing];

[self clearLayout];

_cachedAttributedText = [attributedText copy];
_cachedTextAttributes = textAttributes;

return _cachedAttributedText;
}

- (void)dirtyLayout
{
[super dirtyLayout];
_cachedAttributedText = nil;
_cachedTextAttributes = nil;
}

- (void)didUpdateReactSubviews
{
[super didUpdateReactSubviews];
[self dirtyLayout];
}

- (void)didSetProps:(NSArray<NSString *> *)changedProps
{
[super didSetProps:changedProps];
[self dirtyLayout];
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/

@protocol RCTFontAttributesDelegate <NSObject>
#import <React/RCTViewManager.h>

- (void)fontAttributesDidChangeWithFont:(UIFont *)font;
NS_ASSUME_NONNULL_BEGIN

@interface RCTBaseTextViewManager : RCTViewManager

@end

NS_ASSUME_NONNULL_END
57 changes: 57 additions & 0 deletions Libraries/Text/BaseText/RCTBaseTextViewManager.m
Original file line number Diff line number Diff line change
@@ -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
8 changes: 6 additions & 2 deletions Libraries/Text/RCTConvert+Text.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@

#import <React/RCTConvert.h>

NS_ASSUME_NONNULL_BEGIN

@interface RCTConvert (Text)

+ (UITextAutocorrectionType)UITextAutocorrectionType:(id)json;
+ (UITextSpellCheckingType)UITextSpellCheckingType:(id)json;
+ (UITextAutocorrectionType)UITextAutocorrectionType:(nullable id)json;
+ (UITextSpellCheckingType)UITextSpellCheckingType:(nullable id)json;

@end

NS_ASSUME_NONNULL_END
31 changes: 0 additions & 31 deletions Libraries/Text/RCTFontAttributes.h

This file was deleted.

112 changes: 0 additions & 112 deletions Libraries/Text/RCTFontAttributes.m

This file was deleted.

Loading

1 comment on commit 2716f53

@filipef101
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shergin should this be the cause for #18219 ?

Please sign in to comment.