Permalink
Browse files

Fixed crash in RCTText due to NSTextContainer/NSLayoutManager being a…

…ccessed concurrently from main and shadow queues
  • Loading branch information...
nicklockwood committed May 27, 2015
1 parent 95517fc commit 0689c0790edcc9cb8494e3f15300e2743556f442
@@ -14,8 +14,6 @@ extern NSString *const RCTReactTagAttributeName;
@interface RCTShadowText : RCTShadowView
@property (nonatomic, assign) NSWritingDirection writingDirection;
@property (nonatomic, strong) UIColor *textBackgroundColor;
@property (nonatomic, strong) UIColor *color;
@property (nonatomic, copy) NSString *fontFamily;
@property (nonatomic, assign) CGFloat fontSize;
@@ -24,17 +22,12 @@ extern NSString *const RCTReactTagAttributeName;
@property (nonatomic, assign) BOOL isHighlighted;
@property (nonatomic, assign) CGFloat letterSpacing;
@property (nonatomic, assign) CGFloat lineHeight;
@property (nonatomic, assign) NSUInteger maximumNumberOfLines;
@property (nonatomic, assign) NSUInteger numberOfLines;
@property (nonatomic, assign) CGSize shadowOffset;
@property (nonatomic, assign) NSTextAlignment textAlign;
@property (nonatomic, strong) UIColor *textBackgroundColor;
@property (nonatomic, assign) NSWritingDirection writingDirection;
// Not exposed to JS
@property (nonatomic, strong) UIFont *font;
@property (nonatomic, assign) NSLineBreakMode truncationMode;
@property (nonatomic, assign) CGFloat effectiveLetterSpacing;
@property (nonatomic, copy, readonly) NSAttributedString *attributedString;
@property (nonatomic, strong, readonly) NSLayoutManager *layoutManager;
@property (nonatomic, strong, readonly) NSTextContainer *textContainer;
- (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width;
@end
@@ -17,60 +17,59 @@
NSString *const RCTIsHighlightedAttributeName = @"IsHighlightedAttributeName";
NSString *const RCTReactTagAttributeName = @"ReactTagAttributeName";
@implementation RCTShadowText
{
NSAttributedString *_cachedAttributedString;
CGFloat _effectiveLetterSpacing;
}
static css_dim_t RCTMeasure(void *context, float width)
{
RCTShadowText *shadowText = (__bridge RCTShadowText *)context;
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:[shadowText attributedString]];
NSTextStorage *previousTextStorage = shadowText.layoutManager.textStorage;
if (previousTextStorage) {
[previousTextStorage removeLayoutManager:shadowText.layoutManager];
}
[textStorage addLayoutManager:shadowText.layoutManager];
shadowText.textContainer.size = CGSizeMake(isnan(width) ? CGFLOAT_MAX : width, CGFLOAT_MAX);
[shadowText.layoutManager ensureLayoutForTextContainer:shadowText.textContainer];
CGSize computedSize = [shadowText.layoutManager usedRectForTextContainer:shadowText.textContainer].size;
[textStorage removeLayoutManager:shadowText.layoutManager];
if (previousTextStorage) {
[previousTextStorage addLayoutManager:shadowText.layoutManager];
}
NSTextStorage *textStorage = [shadowText buildTextStorageForWidth:width];
NSLayoutManager *layoutManager = [textStorage.layoutManagers firstObject];
NSTextContainer *textContainer = [layoutManager.textContainers firstObject];
CGSize computedSize = [layoutManager usedRectForTextContainer:textContainer].size;
css_dim_t result;
result.dimensions[CSS_WIDTH] = RCTCeilPixelValue(computedSize.width);
if (shadowText.effectiveLetterSpacing < 0) {
result.dimensions[CSS_WIDTH] -= shadowText.effectiveLetterSpacing;
if (shadowText->_effectiveLetterSpacing < 0) {
result.dimensions[CSS_WIDTH] -= shadowText->_effectiveLetterSpacing;
}
result.dimensions[CSS_HEIGHT] = RCTCeilPixelValue(computedSize.height);
return result;
}
@implementation RCTShadowText
{
NSLayoutManager *_layoutManager;
NSTextContainer *_textContainer;
NSAttributedString *_cachedAttributedString;
UIFont *_font;
}
- (instancetype)init
{
if ((self = [super init])) {
_fontSize = NAN;
_letterSpacing = NAN;
_isHighlighted = NO;
}
return self;
}
_textContainer = [[NSTextContainer alloc] init];
_textContainer.lineBreakMode = NSLineBreakByTruncatingTail;
_textContainer.lineFragmentPadding = 0.0;
- (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width
{
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
_layoutManager = [[NSLayoutManager alloc] init];
[_layoutManager addTextContainer:_textContainer];
}
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:self.attributedString];
[textStorage addLayoutManager:layoutManager];
return self;
NSTextContainer *textContainer = [[NSTextContainer alloc] init];
textContainer.lineFragmentPadding = 0.0;
textContainer.lineBreakMode = _numberOfLines > 0 ? NSLineBreakByTruncatingTail : NSLineBreakByClipping;
textContainer.maximumNumberOfLines = _numberOfLines;
UIEdgeInsets padding = self.paddingAsInsets;
width -= (padding.left + padding.right);
textContainer.size = (CGSize){isnan(width) ? CGFLOAT_MAX : width, CGFLOAT_MAX};
[layoutManager addTextContainer:textContainer];
[layoutManager ensureLayoutForTextContainer:textContainer];
return textStorage;
}
- (NSAttributedString *)attributedString
@@ -135,8 +134,8 @@ - (NSAttributedString *)_attributedStringWithFontFamily:(NSString *)fontFamily
[self _addAttribute:NSBackgroundColorAttributeName withValue:self.textBackgroundColor toAttributedString:attributedString];
}
_font = [RCTConvert UIFont:nil withFamily:fontFamily size:fontSize weight:fontWeight style:fontStyle];
[self _addAttribute:NSFontAttributeName withValue:_font toAttributedString:attributedString];
UIFont *font = [RCTConvert UIFont:nil withFamily:fontFamily size:fontSize weight:fontWeight style:fontStyle];
[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];
@@ -148,11 +147,6 @@ - (NSAttributedString *)_attributedStringWithFontFamily:(NSString *)fontFamily
return _cachedAttributedString;
}
- (UIFont *)font
{
return _font ?: [RCTConvert UIFont:nil withFamily:_fontFamily size:@(_fontSize) weight:_fontWeight style:_fontStyle];
}
- (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) {
@@ -231,38 +225,18 @@ - (void)set##setProp:(type)value; \
[self dirtyText]; \
}
RCT_TEXT_PROPERTY(TextBackgroundColor, _textBackgroundColor, UIColor *);
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(LetterSpacing, _letterSpacing, CGFloat);
RCT_TEXT_PROPERTY(LineHeight, _lineHeight, CGFloat);
RCT_TEXT_PROPERTY(ShadowOffset, _shadowOffset, CGSize);
RCT_TEXT_PROPERTY(TextAlign, _textAlign, NSTextAlignment);
RCT_TEXT_PROPERTY(IsHighlighted, _isHighlighted, BOOL);
RCT_TEXT_PROPERTY(Font, _font, UIFont *);
- (NSLineBreakMode)truncationMode
{
return _textContainer.lineBreakMode;
}
- (void)setTruncationMode:(NSLineBreakMode)truncationMode
{
_textContainer.lineBreakMode = truncationMode;
[self dirtyText];
}
- (NSUInteger)maximumNumberOfLines
{
return _textContainer.maximumNumberOfLines;
}
- (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines
{
_textContainer.maximumNumberOfLines = maximumNumberOfLines;
[self dirtyText];
}
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(IsHighlighted, _isHighlighted, BOOL)
RCT_TEXT_PROPERTY(LetterSpacing, _letterSpacing, CGFloat)
RCT_TEXT_PROPERTY(LineHeight, _lineHeight, CGFloat)
RCT_TEXT_PROPERTY(NumberOfLines, _numberOfLines, NSUInteger)
RCT_TEXT_PROPERTY(ShadowOffset, _shadowOffset, CGSize)
RCT_TEXT_PROPERTY(TextAlign, _textAlign, NSTextAlignment)
RCT_TEXT_PROPERTY(TextBackgroundColor, _textBackgroundColor, UIColor *)
RCT_TEXT_PROPERTY(WritingDirection, _writingDirection, NSWritingDirection)
@end
View
@@ -11,9 +11,7 @@
@interface RCTText : UIView
@property (nonatomic, strong) NSLayoutManager *layoutManager;
@property (nonatomic, strong) NSTextContainer *textContainer;
@property (nonatomic, copy) NSAttributedString *attributedText;
@property (nonatomic, assign) UIEdgeInsets contentInset;
@property (nonatomic, strong) NSTextStorage *textStorage;
@end
View
@@ -15,9 +15,7 @@
@implementation RCTText
{
NSLayoutManager *_layoutManager;
NSTextStorage *_textStorage;
NSTextContainer *_textContainer;
}
- (instancetype)initWithFrame:(CGRect)frame
@@ -31,103 +29,42 @@ - (instancetype)initWithFrame:(CGRect)frame
self.opaque = NO;
self.contentMode = UIViewContentModeRedraw;
}
return self;
}
- (NSAttributedString *)attributedText
{
return [_textStorage copy];
}
- (void)setAttributedText:(NSAttributedString *)attributedText
{
for (NSLayoutManager *existingLayoutManager in _textStorage.layoutManagers) {
[_textStorage removeLayoutManager:existingLayoutManager];
}
_textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedText];
if (_layoutManager) {
[_textStorage addLayoutManager:_layoutManager];
}
[self setNeedsDisplay];
}
- (void)setTextContainer:(NSTextContainer *)textContainer
- (void)setTextStorage:(NSTextStorage *)textStorage
{
if ([_textContainer isEqual:textContainer]) {
return;
}
_textContainer = textContainer;
for (NSInteger i = _layoutManager.textContainers.count - 1; i >= 0; i--) {
[_layoutManager removeTextContainerAtIndex:i];
}
if (_textContainer) {
[_layoutManager addTextContainer:_textContainer];
}
_textStorage = textStorage;
[self setNeedsDisplay];
}
- (void)setLayoutManager:(NSLayoutManager *)layoutManager
{
if ([_layoutManager isEqual:layoutManager]) {
return;
}
_layoutManager = layoutManager;
for (NSLayoutManager *existingLayoutManager in _textStorage.layoutManagers) {
[_textStorage removeLayoutManager:existingLayoutManager];
}
if (_layoutManager) {
[_textStorage addLayoutManager:_layoutManager];
}
[self setNeedsDisplay];
}
- (CGRect)textFrame
{
return UIEdgeInsetsInsetRect(self.bounds, _contentInset);
}
- (void)drawRect:(CGRect)rect
{
CGRect textFrame = [self textFrame];
// We reset the text container size every time because RCTShadowText's
// RCTMeasure overrides it. The header comment for `size` says that a height
// of 0.0 should be enough, but it isn't.
_textContainer.size = CGSizeMake(textFrame.size.width, CGFLOAT_MAX);
NSRange glyphRange = [_layoutManager glyphRangeForTextContainer:_textContainer];
[_layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:textFrame.origin];
[_layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:textFrame.origin];
NSLayoutManager *layoutManager = [_textStorage.layoutManagers firstObject];
NSTextContainer *textContainer = [layoutManager.textContainers firstObject];
CGRect textFrame = UIEdgeInsetsInsetRect(self.bounds, _contentInset);
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:textFrame.origin];
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:textFrame.origin];
}
- (NSNumber *)reactTagAtPoint:(CGPoint)point
{
CGFloat fraction;
NSUInteger characterIndex = [_layoutManager characterIndexForPoint:point
inTextContainer:_textContainer
fractionOfDistanceBetweenInsertionPoints:&fraction];
NSNumber *reactTag = self.reactTag;
NSNumber *reactTag = nil;
CGFloat fraction;
NSLayoutManager *layoutManager = [_textStorage.layoutManagers firstObject];
NSTextContainer *textContainer = [layoutManager.textContainers firstObject];
NSUInteger characterIndex = [layoutManager characterIndexForPoint:point
inTextContainer:textContainer
fractionOfDistanceBetweenInsertionPoints:&fraction];
// 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];
}
return reactTag ?: self.reactTag;
return reactTag;
}
#pragma mark - Accessibility
Oops, something went wrong.

0 comments on commit 0689c07

Please sign in to comment.