Fixed text update on OSS

This commit is contained in:
Nick Lockwood 2015-06-01 08:34:09 -07:00
parent a2db4a4a5b
commit 49e87af934
6 changed files with 89 additions and 26 deletions

View File

@ -28,7 +28,6 @@ extern NSString *const RCTReactTagAttributeName;
@property (nonatomic, strong) UIColor *textBackgroundColor;
@property (nonatomic, assign) NSWritingDirection writingDirection;
- (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width;
- (void)recomputeText;
@end

View File

@ -12,6 +12,8 @@
#import "RCTConvert.h"
#import "RCTLog.h"
#import "RCTShadowRawText.h"
#import "RCTSparseArray.h"
#import "RCTText.h"
#import "RCTUtils.h"
NSString *const RCTIsHighlightedAttributeName = @"IsHighlightedAttributeName";
@ -19,6 +21,8 @@ NSString *const RCTReactTagAttributeName = @"ReactTagAttributeName";
@implementation RCTShadowText
{
NSTextStorage *_cachedTextStorage;
CGFloat _cachedTextStorageWidth;
NSAttributedString *_cachedAttributedString;
CGFloat _effectiveLetterSpacing;
}
@ -50,8 +54,35 @@ static css_dim_t RCTMeasure(void *context, float width)
return self;
}
- (NSDictionary *)processUpdatedProperties:(NSMutableSet *)applierBlocks
parentProperties:(NSDictionary *)parentProperties
{
parentProperties = [super processUpdatedProperties:applierBlocks
parentProperties:parentProperties];
NSTextStorage *textStorage = [self buildTextStorageForWidth:self.frame.size.width];
[applierBlocks addObject:^(RCTSparseArray *viewRegistry) {
RCTText *view = viewRegistry[self.reactTag];
view.textStorage = textStorage;
}];
return parentProperties;
}
- (void)applyLayoutNode:(css_node_t *)node
viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame
absolutePosition:(CGPoint)absolutePosition
{
[super applyLayoutNode:node viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition];
[self dirtyPropagation];
}
- (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width
{
if (_cachedTextStorage && width == _cachedTextStorageWidth) {
return _cachedTextStorage;
}
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:self.attributedString];
@ -69,13 +100,23 @@ static css_dim_t RCTMeasure(void *context, float width)
[layoutManager addTextContainer:textContainer];
[layoutManager ensureLayoutForTextContainer:textContainer];
_cachedTextStorage = textStorage;
_cachedTextStorageWidth = width;
return textStorage;
}
- (void)dirtyText
{
[super dirtyText];
_cachedTextStorage = nil;
}
- (void)recomputeText
{
[self attributedString];
[self setTextComputed];
[self dirtyPropagation];
}
- (NSAttributedString *)attributedString

View File

@ -96,12 +96,10 @@ RCT_EXPORT_SHADOW_PROPERTY(numberOfLines, NSUInteger)
{
NSNumber *reactTag = shadowView.reactTag;
UIEdgeInsets padding = shadowView.paddingAsInsets;
NSTextStorage *textStorage = [shadowView buildTextStorageForWidth:shadowView.frame.size.width];
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
RCTText *text = viewRegistry[reactTag];
text.contentInset = padding;
text.textStorage = textStorage;
};
}

View File

@ -481,6 +481,10 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa
shadowView.newView = NO;
}
// 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.
NSMutableArray *updateBlocks = [[NSMutableArray alloc] init];
for (RCTShadowView *shadowView in viewsWithNewFrames) {
RCTViewManager *manager = _viewManagerRegistry[shadowView.reactTag];

View File

@ -20,8 +20,7 @@ typedef NS_ENUM(NSUInteger, RCTUpdateLifecycle) {
RCTUpdateLifecycleDirtied,
};
// TODO: is this redundact now?
typedef void (^RCTApplierBlock)(RCTSparseArray *);
typedef void (^RCTApplierBlock)(RCTSparseArray *viewRegistry);
/**
* ShadowView tree mirrors RCT view tree. Every node is highly stateful.
@ -117,34 +116,48 @@ typedef void (^RCTApplierBlock)(RCTSparseArray *);
* The applierBlocks set contains RCTApplierBlock functions that must be applied
* on the main thread in order to update the view.
*/
- (void)collectUpdatedProperties:(NSMutableSet *)applierBlocks parentProperties:(NSDictionary *)parentProperties;
- (void)collectUpdatedProperties:(NSMutableSet *)applierBlocks
parentProperties:(NSDictionary *)parentProperties;
/**
* Process the updated properties and apply them to view. Shadow view classes
* that add additional propagating properties should override this method.
*/
- (NSDictionary *)processUpdatedProperties:(NSMutableSet *)applierBlocks
parentProperties:(NSDictionary *)parentProperties NS_REQUIRES_SUPER;
/**
* Calculate all views whose frame needs updating after layout has been calculated.
* The viewsWithNewFrame set contains the reactTags of the views that need updating.
*/
- (void)collectRootUpdatedFrames:(NSMutableSet *)viewsWithNewFrame parentConstraint:(CGSize)parentConstraint;
- (void)collectRootUpdatedFrames:(NSMutableSet *)viewsWithNewFrame
parentConstraint:(CGSize)parentConstraint;
/**
* Recursively apply layout to children.
*/
- (void)applyLayoutNode:(css_node_t *)node
viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame
absolutePosition:(CGPoint)absolutePosition NS_REQUIRES_SUPER;
/**
* The following are implementation details exposed to subclasses. Do not call them directly
*/
- (void)fillCSSNode:(css_node_t *)node;
- (void)dirtyLayout;
- (void)fillCSSNode:(css_node_t *)node NS_REQUIRES_SUPER;
- (void)dirtyLayout NS_REQUIRES_SUPER;
- (BOOL)isLayoutDirty;
// TODO: is this still needed?
- (void)dirtyPropagation;
- (void)dirtyPropagation NS_REQUIRES_SUPER;
- (BOOL)isPropagationDirty;
// TODO: move this to text node?
- (void)dirtyText;
- (void)dirtyText NS_REQUIRES_SUPER;
- (void)setTextComputed NS_REQUIRES_SUPER;
- (BOOL)isTextDirty;
- (void)setTextComputed;
/**
* Triggers a recalculation of the shadow view's layout.
*/
- (void)updateLayout;
- (void)updateLayout NS_REQUIRES_SUPER;
/**
* Computes the recursive offset, meaning the sum of all descendant offsets -

View File

@ -120,7 +120,9 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
// width = 213.5 - 106.5 = 107
// You'll notice that this is the same width we calculated for the parent view because we've taken its position into account.
- (void)applyLayoutNode:(css_node_t *)node viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame absolutePosition:(CGPoint)absolutePosition
- (void)applyLayoutNode:(css_node_t *)node
viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame
absolutePosition:(CGPoint)absolutePosition
{
if (!node->layout.should_update) {
return;
@ -161,12 +163,19 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
for (int i = 0; i < node->children_count; ++i) {
RCTShadowView *child = (RCTShadowView *)_reactSubviews[i];
[child applyLayoutNode:node->get_child(node->context, i) viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition];
[child applyLayoutNode:node->get_child(node->context, i)
viewsWithNewFrame:viewsWithNewFrame
absolutePosition:absolutePosition];
}
}
- (NSDictionary *)processBackgroundColor:(NSMutableSet *)applierBlocks parentProperties:(NSDictionary *)parentProperties
- (NSDictionary *)processUpdatedProperties:(NSMutableSet *)applierBlocks
parentProperties:(NSDictionary *)parentProperties
{
// TODO: we always refresh all propagated properties when propagation is
// dirtied, but really we should track which properties have changed and
// only update those.
if (!_backgroundColor) {
UIColor *parentBackgroundColor = parentProperties[RCTBackgroundColorProp];
if (parentBackgroundColor) {
@ -190,14 +199,15 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
return parentProperties;
}
- (void)collectUpdatedProperties:(NSMutableSet *)applierBlocks parentProperties:(NSDictionary *)parentProperties
- (void)collectUpdatedProperties:(NSMutableSet *)applierBlocks
parentProperties:(NSDictionary *)parentProperties
{
if (_propagationLifecycle == RCTUpdateLifecycleComputed && [parentProperties isEqualToDictionary:_lastParentProperties]) {
return;
}
_propagationLifecycle = RCTUpdateLifecycleComputed;
_lastParentProperties = parentProperties;
NSDictionary *nextProps = [self processBackgroundColor:applierBlocks parentProperties:parentProperties];
NSDictionary *nextProps = [self processUpdatedProperties:applierBlocks parentProperties:parentProperties];
for (RCTShadowView *child in _reactSubviews) {
[child collectUpdatedProperties:applierBlocks parentProperties:nextProps];
}
@ -212,21 +222,19 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
- (CGRect)measureLayoutRelativeToAncestor:(RCTShadowView *)ancestor
{
CGFloat totalOffsetTop = 0.0;
CGFloat totalOffsetLeft = 0.0;
CGSize size = self.frame.size;
CGPoint offset = CGPointZero;
NSInteger depth = 30; // max depth to search
RCTShadowView *shadowView = self;
while (depth && shadowView && shadowView != ancestor) {
totalOffsetTop += shadowView.frame.origin.y;
totalOffsetLeft += shadowView.frame.origin.x;
offset.x += shadowView.frame.origin.x;
offset.y += shadowView.frame.origin.y;
shadowView = shadowView->_superview;
depth--;
}
if (ancestor != shadowView) {
return CGRectNull;
}
return (CGRect){{totalOffsetLeft, totalOffsetTop}, size};
return (CGRect){offset, self.frame.size};
}
- (instancetype)init