diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index fedcfaa091..a7d00a9968 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -11,22 +11,19 @@ #import "ASTextNode.h" #import "ASTextNode+Beta.h" +#include + #import -#import #import #import #import #import #import "ASTextKitCoreTextAdditions.h" -#import "ASTextKitComponents.h" -#import "ASTextKitFontSizeAdjuster.h" -#import "ASTextKitRenderer.h" #import "ASTextKitRenderer+Positioning.h" #import "ASTextKitShadower.h" #import "ASInternalHelpers.h" -#import "ASEqualityHelpers.h" #import "ASLayout.h" static const NSTimeInterval ASTextNodeHighlightFadeOutDuration = 0.15; @@ -38,15 +35,13 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation @interface ASTextNodeDrawParameters : NSObject @property (nonatomic, assign, readonly) CGRect bounds; - @property (nonatomic, strong, readonly) UIColor *backgroundColor; @end @implementation ASTextNodeDrawParameters -- (instancetype)initWithBounds:(CGRect)bounds - backgroundColor:(UIColor *)backgroundColor +- (instancetype)initWithBounds:(CGRect)bounds backgroundColor:(UIColor *)backgroundColor { if (self = [super init]) { _bounds = bounds; @@ -76,7 +71,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation NSRange _highlightRange; ASHighlightOverlayLayer *_activeHighlightLayer; - ASDN::Mutex _rendererLock; + std::recursive_mutex _textLock; CGSize _constrainedSize; @@ -160,6 +155,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (NSString *)description { + std::lock_guard l(_textLock); + NSString *plainString = [[_attributedText string] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]; NSString *truncationString = [_composedTruncationText string]; if (plainString.length > 50) @@ -195,7 +192,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (void)didLoad { [super didLoad]; - + // If we are view-backed and the delegate cares, support the long-press callback. SEL longPressCallback = @selector(textNode:longPressedLinkAttribute:value:atPoint:textRange:); if (!self.isLayerBacked && [self.delegate respondsToSelector:longPressCallback]) { @@ -227,7 +224,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (ASTextKitRenderer *)_rendererWithBounds:(CGRect)bounds { - ASDN::MutexLocker l(_rendererLock); + std::lock_guard l(_textLock); + if (_renderer == nil) { CGSize constrainedSize = _constrainedSize.width != -INFINITY ? _constrainedSize : bounds.size; _renderer = [[ASTextKitRenderer alloc] initWithTextKitAttributes:[self _rendererAttributes] @@ -250,9 +248,28 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; }; } +- (void)_invalidateRendererIfNeeded +{ + [self _invalidateRendererIfNeededForBoundsSize:self.threadSafeBounds.size]; +} + +- (void)_invalidateRendererIfNeededForBoundsSize:(CGSize)boundsSize +{ + if ([self _needInvalidateRendererForBoundsSize:boundsSize]) { + // Our bounds of frame have changed to a size that is not identical to our constraining size, + // so our previous layout information is invalid, and TextKit may draw at the + // incorrect origin. + { + std::lock_guard l(_textLock); + _constrainedSize = CGSizeMake(-INFINITY, -INFINITY); + } + [self _invalidateRenderer]; + } +} + - (void)_invalidateRenderer { - ASDN::MutexLocker l(_rendererLock); + std::lock_guard l(_textLock); if (_renderer) { // Destruction of the layout managers/containers/text storage is quite @@ -267,27 +284,13 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; } } -- (void)_invalidateRendererIfNeeded -{ - [self _invalidateRendererIfNeededForBoundsSize:self.threadSafeBounds.size]; -} - -- (void)_invalidateRendererIfNeededForBoundsSize:(CGSize)boundsSize -{ - if ([self _needInvalidateRendererForBoundsSize:boundsSize]) { - // Our bounds of frame have changed to a size that is not identical to our constraining size, - // so our previous layout information is invalid, and TextKit may draw at the - // incorrect origin. - _constrainedSize = CGSizeMake(-INFINITY, -INFINITY); - [self _invalidateRenderer]; - } -} - #pragma mark - Layout and Sizing - (BOOL)_needInvalidateRendererForBoundsSize:(CGSize)boundsSize { - if (!_renderer) { + std::lock_guard l(_textLock); + + if (_renderer == nil) { return YES; } @@ -322,6 +325,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; [super calculatedLayoutDidChange]; ASLayout *layout = self.calculatedLayout; + + std::lock_guard l(_textLock); if (layout != nil) { _constrainedSize = layout.size; _renderer.constrainedSize = layout.size; @@ -333,6 +338,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; ASDisplayNodeAssert(constrainedSize.width >= 0, @"Constrained width for text (%f) is too narrow", constrainedSize.width); ASDisplayNodeAssert(constrainedSize.height >= 0, @"Constrained height for text (%f) is too short", constrainedSize.height); + std::lock_guard l(_textLock); + _constrainedSize = constrainedSize; // Instead of invalidating the renderer, in case this is a new call with a different constrained size, @@ -341,7 +348,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; [self setNeedsDisplay]; - CGSize size = [[self _renderer] size]; + CGSize size = [self _renderer].size; if (_attributedText.length > 0) { CGFloat screenScale = ASScreenScale(); self.ascender = round([[_attributedText attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender] * screenScale)/screenScale; @@ -359,6 +366,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (void)setAttributedText:(NSAttributedString *)attributedText { + std::lock_guard l(_textLock); + if (attributedText == nil) { attributedText = [[NSAttributedString alloc] initWithString:@"" attributes:nil]; } @@ -387,17 +396,19 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; [self invalidateCalculatedLayout]; [self setNeedsDisplay]; - + + + // Accessiblity self.accessibilityLabel = _attributedText.string; - - // We're an accessibility element by default if there is a string. - self.isAccessibilityElement = _attributedText.length != 0; + self.isAccessibilityElement = (_attributedText.length != 0); // We're an accessibility element by default if there is a string. } #pragma mark - Text Layout - (void)setExclusionPaths:(NSArray *)exclusionPaths { + std::lock_guard l(_textLock); + if (ASObjectIsEqual(exclusionPaths, _exclusionPaths)) { return; } @@ -410,6 +421,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (NSArray *)exclusionPaths { + std::lock_guard l(_textLock); + return _exclusionPaths; } @@ -417,6 +430,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (void)drawRect:(CGRect)bounds withParameters:(ASTextNodeDrawParameters *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing { + std::lock_guard l(_textLock); + CGContextRef context = UIGraphicsGetCurrentContext(); ASDisplayNodeAssert(context, @"This is no good without a context."); @@ -437,7 +452,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; } // Draw shadow - [[renderer shadower] setShadowInContext:context]; + [renderer.shadower setShadowInContext:context]; // Draw text bounds.origin = textOrigin; @@ -470,6 +485,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; inAdditionalTruncationMessage:(out BOOL *)inAdditionalTruncationMessageOut forHighlighting:(BOOL)highlighting { + ASDisplayNodeAssertMainThread(); + ASTextKitRenderer *renderer = [self _renderer]; NSRange visibleRange = renderer.firstVisibleRange; NSAttributedString *attributedString = _attributedText; @@ -562,6 +579,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { + ASDisplayNodeAssertMainThread(); + if (gestureRecognizer == _longPressGestureRecognizer) { // Don't allow long press on truncation message if ([self _pendingTruncationTap]) { @@ -593,6 +612,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (NSRange)highlightRange { + ASDisplayNodeAssertMainThread(); + return _highlightRange; } @@ -603,6 +624,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (void)setHighlightRange:(NSRange)highlightRange animated:(BOOL)animated { + ASDisplayNodeAssertMainThread(); + [self _setHighlightRange:highlightRange forAttributeName:nil value:nil animated:animated]; } @@ -672,7 +695,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; NSMutableArray *converted = [NSMutableArray arrayWithCapacity:highlightRects.count]; for (NSValue *rectValue in highlightRects) { UIEdgeInsets shadowPadding = _renderer.shadower.shadowPadding; - CGRect rendererRect = [[self class] _adjustRendererRect:rectValue.CGRectValue forShadowPadding:shadowPadding]; + CGRect rendererRect = ASTextNodeAdjustRenderRectForShadowPadding(rectValue.CGRectValue, shadowPadding); CGRect highlightedRect = [self.layer convertRect:rendererRect toLayer:highlightTargetLayer]; // We set our overlay layer's frame to the bounds of the highlight target layer. @@ -709,6 +732,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (void)_clearHighlightIfNecessary { + ASDisplayNodeAssertMainThread(); + if ([self _pendingLinkTap] || [self _pendingTruncationTap]) { [self setHighlightRange:NSMakeRange(0, 0) animated:YES]; } @@ -726,29 +751,12 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; #pragma mark - Text rects -+ (CGRect)_adjustRendererRect:(CGRect)rendererRect forShadowPadding:(UIEdgeInsets)shadowPadding -{ +static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UIEdgeInsets shadowPadding) { rendererRect.origin.x -= shadowPadding.left; rendererRect.origin.y -= shadowPadding.top; return rendererRect; } -- (NSArray *)_rectsForTextRange:(NSRange)textRange measureOption:(ASTextKitRendererMeasureOption)measureOption -{ - NSArray *rects = [[self _renderer] rectsForTextRange:textRange measureOption:measureOption]; - NSMutableArray *adjustedRects = [NSMutableArray array]; - - for (NSValue *rectValue in rects) { - CGRect rect = [rectValue CGRectValue]; - rect = [self.class _adjustRendererRect:rect forShadowPadding:self.shadowPadding]; - - NSValue *adjustedRectValue = [NSValue valueWithCGRect:rect]; - [adjustedRects addObject:adjustedRectValue]; - } - - return adjustedRects; -} - - (NSArray *)rectsForTextRange:(NSRange)textRange { return [self _rectsForTextRange:textRange measureOption:ASTextKitRendererMeasureOptionCapHeight]; @@ -759,22 +767,46 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; return [self _rectsForTextRange:textRange measureOption:ASTextKitRendererMeasureOptionBlock]; } +- (NSArray *)_rectsForTextRange:(NSRange)textRange measureOption:(ASTextKitRendererMeasureOption)measureOption +{ + std::lock_guard l(_textLock); + + NSArray *rects = [[self _renderer] rectsForTextRange:textRange measureOption:measureOption]; + NSMutableArray *adjustedRects = [NSMutableArray array]; + + for (NSValue *rectValue in rects) { + CGRect rect = [rectValue CGRectValue]; + rect = ASTextNodeAdjustRenderRectForShadowPadding(rect, self.shadowPadding); + + NSValue *adjustedRectValue = [NSValue valueWithCGRect:rect]; + [adjustedRects addObject:adjustedRectValue]; + } + + return adjustedRects; +} + - (CGRect)trailingRect { + std::lock_guard l(_textLock); + CGRect rect = [[self _renderer] trailingRect]; - return [self.class _adjustRendererRect:rect forShadowPadding:self.shadowPadding]; + return ASTextNodeAdjustRenderRectForShadowPadding(rect, self.shadowPadding); } - (CGRect)frameForTextRange:(NSRange)textRange { + std::lock_guard l(_textLock); + CGRect frame = [[self _renderer] frameForTextRange:textRange]; - return [self.class _adjustRendererRect:frame forShadowPadding:self.shadowPadding]; + return ASTextNodeAdjustRenderRectForShadowPadding(frame, self.shadowPadding); } #pragma mark - Placeholders - (void)setPlaceholderColor:(UIColor *)placeholderColor { + std::lock_guard l(_textLock); + _placeholderColor = placeholderColor; // prevent placeholders if we don't have a color @@ -790,6 +822,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; return nil; } + std::lock_guard l(_textLock); + UIGraphicsBeginImageContext(size); [self.placeholderColor setFill]; @@ -816,8 +850,10 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; #pragma mark - Touch Handling --(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event +- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { + ASDisplayNodeAssertMainThread(); + if (!_passthroughNonlinkTouches) { return [super pointInside:point withEvent:event]; } @@ -846,9 +882,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { - [super touchesBegan:touches withEvent:event]; - ASDisplayNodeAssertMainThread(); + [super touchesBegan:touches withEvent:event]; CGPoint point = [[touches anyObject] locationInView:self.view]; @@ -878,15 +913,17 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { + ASDisplayNodeAssertMainThread(); [super touchesCancelled:touches withEvent:event]; - + [self _clearHighlightIfNecessary]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { + ASDisplayNodeAssertMainThread(); [super touchesEnded:touches withEvent:event]; - + if ([self _pendingLinkTap] && [_delegate respondsToSelector:@selector(textNode:tappedLinkAttribute:value:atPoint:textRange:)]) { CGPoint point = [[touches anyObject] locationInView:self.view]; [_delegate textNode:self tappedLinkAttribute:_highlightedLinkAttributeName value:_highlightedLinkAttributeValue atPoint:point textRange:_highlightRange]; @@ -903,6 +940,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { + ASDisplayNodeAssertMainThread(); [super touchesMoved:touches withEvent:event]; UITouch *touch = [touches anyObject]; @@ -928,6 +966,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (void)_handleLongPress:(UILongPressGestureRecognizer *)longPressRecognizer { + ASDisplayNodeAssertMainThread(); + // Respond to long-press when it begins, not when it ends. if (longPressRecognizer.state == UIGestureRecognizerStateBegan) { if ([self.delegate respondsToSelector:@selector(textNode:longPressedLinkAttribute:value:atPoint:textRange:)]) { @@ -939,11 +979,15 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (BOOL)_pendingLinkTap { + std::lock_guard l(_textLock); + return (_highlightedLinkAttributeValue != nil && ![self _pendingTruncationTap]) && _delegate != nil; } - (BOOL)_pendingTruncationTap { + std::lock_guard l(_textLock); + return [_highlightedLinkAttributeName isEqualToString:ASTextNodeTruncationTokenAttributeName]; } @@ -951,11 +995,15 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (CGColorRef)shadowColor { + std::lock_guard l(_textLock); + return _shadowColor; } - (void)setShadowColor:(CGColorRef)shadowColor { + std::lock_guard l(_textLock); + if (_shadowColor != shadowColor) { if (shadowColor != NULL) { CGColorRetain(shadowColor); @@ -968,11 +1016,15 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (CGSize)shadowOffset { + std::lock_guard l(_textLock); + return _shadowOffset; } - (void)setShadowOffset:(CGSize)shadowOffset { + std::lock_guard l(_textLock); + if (!CGSizeEqualToSize(_shadowOffset, shadowOffset)) { _shadowOffset = shadowOffset; [self _invalidateRenderer]; @@ -982,11 +1034,15 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (CGFloat)shadowOpacity { + std::lock_guard l(_textLock); + return _shadowOpacity; } - (void)setShadowOpacity:(CGFloat)shadowOpacity { + std::lock_guard l(_textLock); + if (_shadowOpacity != shadowOpacity) { _shadowOpacity = shadowOpacity; [self _invalidateRenderer]; @@ -996,11 +1052,15 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (CGFloat)shadowRadius { + std::lock_guard l(_textLock); + return _shadowRadius; } - (void)setShadowRadius:(CGFloat)shadowRadius { + std::lock_guard l(_textLock); + if (_shadowRadius != shadowRadius) { _shadowRadius = shadowRadius; [self _invalidateRenderer]; @@ -1015,6 +1075,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (UIEdgeInsets)shadowPaddingWithRenderer:(ASTextKitRenderer *)renderer { + std::lock_guard l(_textLock); + return renderer.shadower.shadowPadding; } @@ -1032,6 +1094,8 @@ static NSAttributedString *DefaultTruncationAttributedString() - (void)setTruncationAttributedText:(NSAttributedString *)truncationAttributedText { + std::lock_guard l(_textLock); + if (ASObjectIsEqual(_truncationAttributedText, truncationAttributedText)) { return; } @@ -1042,6 +1106,8 @@ static NSAttributedString *DefaultTruncationAttributedString() - (void)setAdditionalTruncationMessage:(NSAttributedString *)additionalTruncationMessage { + std::lock_guard l(_textLock); + if (ASObjectIsEqual(_additionalTruncationMessage, additionalTruncationMessage)) { return; } @@ -1052,6 +1118,8 @@ static NSAttributedString *DefaultTruncationAttributedString() - (void)setTruncationMode:(NSLineBreakMode)truncationMode { + std::lock_guard l(_textLock); + if (_truncationMode != truncationMode) { _truncationMode = truncationMode; [self _invalidateRenderer]; @@ -1061,12 +1129,16 @@ static NSAttributedString *DefaultTruncationAttributedString() - (BOOL)isTruncated { + std::lock_guard l(_textLock); + ASTextKitRenderer *renderer = [self _renderer]; return renderer.firstVisibleRange.length < _attributedText.length; } - (void)setPointSizeScaleFactors:(NSArray *)pointSizeScaleFactors { + std::lock_guard l(_textLock); + if ([_pointSizeScaleFactors isEqualToArray:pointSizeScaleFactors] == NO) { _pointSizeScaleFactors = pointSizeScaleFactors; [self _invalidateRenderer]; @@ -1075,15 +1147,19 @@ static NSAttributedString *DefaultTruncationAttributedString() - (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines { - if (_maximumNumberOfLines != maximumNumberOfLines) { - _maximumNumberOfLines = maximumNumberOfLines; - [self _invalidateRenderer]; - [self setNeedsDisplay]; - } + std::lock_guard l(_textLock); + + if (_maximumNumberOfLines != maximumNumberOfLines) { + _maximumNumberOfLines = maximumNumberOfLines; + [self _invalidateRenderer]; + [self setNeedsDisplay]; + } } - (NSUInteger)lineCount { + std::lock_guard l(_textLock); + return [[self _renderer] lineCount]; } @@ -1091,6 +1167,8 @@ static NSAttributedString *DefaultTruncationAttributedString() - (void)_updateComposedTruncationText { + std::lock_guard l(_textLock); + _composedTruncationText = [self _prepareTruncationStringForDrawing:[self _composedTruncationText]]; } @@ -1107,6 +1185,8 @@ static NSAttributedString *DefaultTruncationAttributedString() */ - (NSRange)_additionalTruncationMessageRangeWithVisibleRange:(NSRange)visibleRange { + std::lock_guard l(_textLock); + // Check if we even have an additional truncation message. if (!_additionalTruncationMessage) { return NSMakeRange(NSNotFound, 0); @@ -1118,8 +1198,7 @@ static NSAttributedString *DefaultTruncationAttributedString() NSUInteger additionalTruncationMessageLength = _additionalTruncationMessage.length; // We get the location of the truncation token, then add the length of the // truncation attributed string +1 for the space between. - NSRange range = NSMakeRange(truncationTokenIndex + _truncationAttributedText.length + 1, additionalTruncationMessageLength); - return range; + return NSMakeRange(truncationTokenIndex + _truncationAttributedText.length + 1, additionalTruncationMessageLength); } /** @@ -1129,6 +1208,8 @@ static NSAttributedString *DefaultTruncationAttributedString() */ - (NSAttributedString *)_composedTruncationText { + std::lock_guard l(_textLock); + //If we have neither return the default if (!_additionalTruncationMessage && !_truncationAttributedText) { return _composedTruncationText; @@ -1157,6 +1238,8 @@ static NSAttributedString *DefaultTruncationAttributedString() */ - (NSAttributedString *)_prepareTruncationStringForDrawing:(NSAttributedString *)truncationString { + std::lock_guard l(_textLock); + truncationString = ASCleanseAttributedStringOfCoreTextAttributes(truncationString); NSMutableAttributedString *truncationMutableString = [truncationString mutableCopy]; // Grab the attributes from the full string