From 2a29f81b3a0fc1a01a97cc22c52b4aae737ed075 Mon Sep 17 00:00:00 2001 From: Tobias Klonk Date: Mon, 20 Apr 2015 10:21:01 +0200 Subject: [PATCH] ASTextNode expose exclusion paths expose NSTextContainer's exclusionPaths property on ASTextNode to be able to exclude view areas from typesetting. This implements #394 --- AsyncDisplayKit/ASTextNode.h | 2 ++ AsyncDisplayKit/ASTextNode.mm | 20 +++++++++++++++++++ AsyncDisplayKit/Details/ASTextNodeRenderer.h | 6 ++++++ AsyncDisplayKit/Details/ASTextNodeRenderer.mm | 16 +++++++++++++++ .../ASTextNodeRendererTests.m | 18 +++++++++++++++++ AsyncDisplayKitTests/ASTextNodeTests.m | 19 ++++++++++++++++++ 6 files changed, 81 insertions(+) diff --git a/AsyncDisplayKit/ASTextNode.h b/AsyncDisplayKit/ASTextNode.h index 11fe337de0..bedbec5b87 100644 --- a/AsyncDisplayKit/ASTextNode.h +++ b/AsyncDisplayKit/ASTextNode.h @@ -76,6 +76,8 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) { */ @property (nonatomic, readonly, assign) NSUInteger lineCount; +@property (nonatomic, strong) NSArray *exclusionPaths; + #pragma mark - Placeholders /** diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index e631f9d28b..85c4640edc 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -87,6 +87,8 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) CGFloat _shadowOpacity; CGFloat _shadowRadius; + NSArray *_exclusionPaths; + NSAttributedString *_composedTruncationString; NSString *_highlightedLinkAttributeName; @@ -281,6 +283,7 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) truncationString:_composedTruncationString truncationMode:_truncationMode maximumLineCount:_maximumLineCount + exclusionPaths:_exclusionPaths constrainedSize:constrainedSize]; } return _renderer; @@ -349,6 +352,23 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) } } +#pragma mark - Text Layout + +- (void)setExclusionPaths:(NSArray *)exclusionPaths +{ + if ((_exclusionPaths == nil && exclusionPaths != nil) || (![_exclusionPaths isEqualToArray:exclusionPaths])) { + _exclusionPaths = exclusionPaths; + [self _invalidateRenderer]; + [self invalidateCalculatedSize]; + [self setNeedsDisplay]; + } +} + +- (NSArray *)exclusionPaths +{ + return _exclusionPaths; +} + #pragma mark - Drawing + (void)drawRect:(CGRect)bounds withParameters:(ASTextNodeDrawParameters *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing diff --git a/AsyncDisplayKit/Details/ASTextNodeRenderer.h b/AsyncDisplayKit/Details/ASTextNodeRenderer.h index dd57a99421..3b80bb1677 100644 --- a/AsyncDisplayKit/Details/ASTextNodeRenderer.h +++ b/AsyncDisplayKit/Details/ASTextNodeRenderer.h @@ -45,6 +45,12 @@ typedef NS_ENUM(NSUInteger, ASTextNodeRendererMeasureOption) { */ @interface ASTextNodeRenderer : NSObject +- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString + truncationString:(NSAttributedString *)truncationString + truncationMode:(NSLineBreakMode)truncationMode + maximumLineCount:(NSUInteger)maximumLineCount + exclusionPaths:(NSArray *)exclusionPaths + constrainedSize:(CGSize)constrainedSize; /* * Designated Initializer * diff --git a/AsyncDisplayKit/Details/ASTextNodeRenderer.mm b/AsyncDisplayKit/Details/ASTextNodeRenderer.mm index e76c8bdbd9..b17ae2833f 100644 --- a/AsyncDisplayKit/Details/ASTextNodeRenderer.mm +++ b/AsyncDisplayKit/Details/ASTextNodeRenderer.mm @@ -39,6 +39,8 @@ static const CGFloat ASTextNodeRendererTextCapHeightPadding = 1.3; NSLayoutManager *_layoutManager; NSTextStorage *_textStorage; NSTextContainer *_textContainer; + + NSArray *_exclusionPaths; } #pragma mark - Initialization @@ -47,6 +49,7 @@ static const CGFloat ASTextNodeRendererTextCapHeightPadding = 1.3; truncationString:(NSAttributedString *)truncationString truncationMode:(NSLineBreakMode)truncationMode maximumLineCount:(NSUInteger)maximumLineCount + exclusionPaths:(NSArray *)exclusionPaths constrainedSize:(CGSize)constrainedSize { if (self = [super init]) { @@ -57,11 +60,22 @@ static const CGFloat ASTextNodeRendererTextCapHeightPadding = 1.3; _maximumLineCount = maximumLineCount; + _exclusionPaths = exclusionPaths; + _constrainedSize = constrainedSize; } return self; } +- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString + truncationString:(NSAttributedString *)truncationString + truncationMode:(NSLineBreakMode)truncationMode + maximumLineCount:(NSUInteger)maximumLineCount + constrainedSize:(CGSize)constrainedSize +{ + return [self initWithAttributedString:attributedString truncationString:truncationString truncationMode:truncationMode maximumLineCount:maximumLineCount exclusionPaths:nil constrainedSize:constrainedSize]; +} + /* * Use this method to lazily construct the TextKit components. */ @@ -97,6 +111,8 @@ static const CGFloat ASTextNodeRendererTextCapHeightPadding = 1.3; // Set maximum number of lines _textContainer.maximumNumberOfLines = _maximumLineCount; + _textContainer.exclusionPaths = _exclusionPaths; + [_layoutManager addTextContainer:_textContainer]; ASDN::StaticMutexUnlocker gu(mutex); diff --git a/AsyncDisplayKitTests/ASTextNodeRendererTests.m b/AsyncDisplayKitTests/ASTextNodeRendererTests.m index fa210c12c1..a9bf427af4 100644 --- a/AsyncDisplayKitTests/ASTextNodeRendererTests.m +++ b/AsyncDisplayKitTests/ASTextNodeRendererTests.m @@ -22,6 +22,7 @@ @property (nonatomic, readwrite, assign) CGFloat lineSpacing; @property (nonatomic, readwrite, assign) CGSize constrainedSize; +@property (nonatomic, readwrite) NSArray *exclusionPaths; @end @@ -43,6 +44,8 @@ _attributedString = [[NSAttributedString alloc] initWithString:@"Lorem ipsum" attributes:attributes]; _truncationString = [[NSAttributedString alloc] initWithString:@"More"]; + _exclusionPaths = nil; + _constrainedSize = CGSizeMake(FLT_MAX, FLT_MAX); } @@ -52,6 +55,7 @@ truncationString:_truncationString truncationMode:_truncationMode maximumLineCount:_maximumLineCount + exclusionPaths:_exclusionPaths constrainedSize:_constrainedSize]; } @@ -141,4 +145,18 @@ }]; } +- (void)testExclusionPaths +{ + _constrainedSize = CGSizeMake(200, CGFLOAT_MAX); + [self setUpRenderer]; + CGSize sizeWithoutExclusionPath = [_renderer size]; + + CGRect exclusionRect = CGRectMake(20, 0, 180, _lineSpacing * 2.0); + _exclusionPaths = @[[UIBezierPath bezierPathWithRect:exclusionRect]]; + [self setUpRenderer]; + CGSize sizeWithExclusionPath = [_renderer size]; + + XCTAssertEqualWithAccuracy(sizeWithoutExclusionPath.height + exclusionRect.size.height, sizeWithExclusionPath.height, 0.5, @"Using an exclusion path so the the text can not fit into the first two lines should increment the size of the text by the heigth of the exclusion path"); +} + @end diff --git a/AsyncDisplayKitTests/ASTextNodeTests.m b/AsyncDisplayKitTests/ASTextNodeTests.m index 6ea99d792a..c4f0789aa1 100644 --- a/AsyncDisplayKitTests/ASTextNodeTests.m +++ b/AsyncDisplayKitTests/ASTextNodeTests.m @@ -177,4 +177,23 @@ XCTAssertFalse(delegate.tappedLinkValue, @"Expected the delegate to be told that the value %@ was tapped, instead it thinks the tapped attribute value is %@", linkAttributeValue, delegate.tappedLinkValue); } +#pragma mark exclusion Paths + +- (void)testSettingExclusionPaths +{ + NSArray *exclusionPaths = @[[UIBezierPath bezierPathWithRect:CGRectMake(10, 20, 30, 40)]]; + _textNode.exclusionPaths = exclusionPaths; + XCTAssertTrue([_textNode.exclusionPaths isEqualToArray:exclusionPaths], @"Failed to set exclusion paths"); +} + +- (void)testAddingExclusionPathsShouldInvalidateAndIncreaseTheSize +{ + CGSize constrainedSize = CGSizeMake(100, CGFLOAT_MAX); + CGSize sizeWithoutExclusionPaths = [_textNode measure:constrainedSize]; + _textNode.exclusionPaths = @[[UIBezierPath bezierPathWithRect:CGRectMake(50, 20, 30, 40)]]; + CGSize sizeWithExclusionPaths = [_textNode measure:constrainedSize]; + + XCTAssertGreaterThan(sizeWithExclusionPaths.height, sizeWithoutExclusionPaths.height, @"Setting exclusions paths should invalidate the calculated size and return a greater size"); +} + @end