diff --git a/AsyncDisplayKit/ASTextNode.h b/AsyncDisplayKit/ASTextNode.h index a3c2b7554a..4e00abe8f4 100644 --- a/AsyncDisplayKit/ASTextNode.h +++ b/AsyncDisplayKit/ASTextNode.h @@ -64,6 +64,12 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) { */ @property (nonatomic, readonly, assign, getter=isTruncated) BOOL truncated; +/** + @abstract The maximum number of lines to render of the text before truncation. + @default 0 (No limit) + */ +@property (nonatomic, assign) NSUInteger maximumLineCount; + /** @abstract The number of lines in the text. Text must have been sized first. */ diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index e845db3c59..161f689fe0 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -243,6 +243,7 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) _renderer = [[ASTextNodeRenderer alloc] initWithAttributedString:_attributedString truncationString:_composedTruncationString truncationMode:_truncationMode + maximumLineCount:_maximumLineCount constrainedSize:constrainedSize]; } return _renderer; @@ -905,6 +906,15 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) return [[self _renderer] truncationStringCharacterRange].location != NSNotFound; } +- (void)setMaximumLineCount:(NSUInteger)maximumLineCount +{ + if (_maximumLineCount != maximumLineCount) { + _maximumLineCount = maximumLineCount; + [self _invalidateRenderer]; + [self setNeedsDisplay]; + } +} + - (NSUInteger)lineCount { return [[self _renderer] lineCount]; diff --git a/AsyncDisplayKit/Details/ASTextNodeRenderer.h b/AsyncDisplayKit/Details/ASTextNodeRenderer.h index aaaaf5a24f..1e2d78a56c 100644 --- a/AsyncDisplayKit/Details/ASTextNodeRenderer.h +++ b/AsyncDisplayKit/Details/ASTextNodeRenderer.h @@ -54,6 +54,7 @@ typedef NS_ENUM(NSUInteger, ASTextNodeRendererMeasureOption) { - (instancetype)initWithAttributedString:(NSAttributedString *)attributedString truncationString:(NSAttributedString *)truncationString truncationMode:(NSLineBreakMode)truncationMode + maximumLineCount:(NSUInteger)maximumLineCount constrainedSize:(CGSize)constrainedSize; #pragma mark - Drawing /* diff --git a/AsyncDisplayKit/Details/ASTextNodeRenderer.mm b/AsyncDisplayKit/Details/ASTextNodeRenderer.mm index d422114cf7..e76c8bdbd9 100644 --- a/AsyncDisplayKit/Details/ASTextNodeRenderer.mm +++ b/AsyncDisplayKit/Details/ASTextNodeRenderer.mm @@ -29,6 +29,7 @@ static const CGFloat ASTextNodeRendererTextCapHeightPadding = 1.3; NSAttributedString *_attributedString; NSAttributedString *_truncationString; NSLineBreakMode _truncationMode; + NSUInteger _maximumLineCount; NSRange _truncationCharacterRange; NSRange _visibleRange; @@ -45,6 +46,7 @@ static const CGFloat ASTextNodeRendererTextCapHeightPadding = 1.3; - (instancetype)initWithAttributedString:(NSAttributedString *)attributedString truncationString:(NSAttributedString *)truncationString truncationMode:(NSLineBreakMode)truncationMode + maximumLineCount:(NSUInteger)maximumLineCount constrainedSize:(CGSize)constrainedSize { if (self = [super init]) { @@ -52,6 +54,8 @@ static const CGFloat ASTextNodeRendererTextCapHeightPadding = 1.3; _truncationString = truncationString; _truncationMode = truncationMode; _truncationCharacterRange = NSMakeRange(NSNotFound, truncationString.length); + + _maximumLineCount = maximumLineCount; _constrainedSize = constrainedSize; } @@ -90,6 +94,8 @@ static const CGFloat ASTextNodeRendererTextCapHeightPadding = 1.3; _textContainer.lineFragmentPadding = 0; // Translate our truncation mode into a line break mode on the container _textContainer.lineBreakMode = _truncationMode; + // Set maximum number of lines + _textContainer.maximumNumberOfLines = _maximumLineCount; [_layoutManager addTextContainer:_textContainer]; diff --git a/AsyncDisplayKitTests/ASTextNodeRendererTests.m b/AsyncDisplayKitTests/ASTextNodeRendererTests.m index f9bfa8c145..076e11b82f 100644 --- a/AsyncDisplayKitTests/ASTextNodeRendererTests.m +++ b/AsyncDisplayKitTests/ASTextNodeRendererTests.m @@ -18,6 +18,7 @@ @property (nonatomic, copy, readwrite) NSAttributedString *attributedString; @property (nonatomic, copy, readwrite) NSAttributedString *truncationString; @property (nonatomic, readwrite, assign) NSLineBreakMode truncationMode; +@property (nonatomic, readwrite, assign) NSUInteger maximumLineCount; @property (nonatomic, readwrite, assign) CGFloat lineSpacing; @property (nonatomic, readwrite, assign) CGSize constrainedSize; @@ -50,6 +51,7 @@ _renderer = [[ASTextNodeRenderer alloc] initWithAttributedString:_attributedString truncationString:_truncationString truncationMode:_truncationMode + maximumLineCount:_maximumLineCount constrainedSize:_constrainedSize]; } @@ -71,6 +73,19 @@ XCTAssertTrue(numberOfLines == 1 , @"If constrained height (%f) is float max, then there should only be one line of text. Size %@", _constrainedSize.width, NSStringFromCGSize(size)); } +- (void)testMaximumLineCount +{ + NSArray *lines = [NSArray arrayWithObjects:@"Hello!", @"world!", @"foo", @"bar", @"baz", nil]; + _maximumLineCount = 2; + for (int i = 0; i <= [lines count]; i++) { + NSString *line = [[lines subarrayWithRange:NSMakeRange(0, i)] componentsJoinedByString:@"\n"]; + _attributedString = [[NSAttributedString alloc] initWithString:line]; + [self setUpRenderer]; + [_renderer size]; + XCTAssertTrue(_renderer.lineCount <= _maximumLineCount, @"The line count %tu after rendering should be no larger than the maximum line count %tu", _renderer.lineCount, _maximumLineCount); + } +} + - (void)testNoTruncationIfEnoughSpace { [self setUpRenderer]; @@ -124,7 +139,6 @@ [_renderer enumerateTextIndexesAtPosition:CGPointZero usingBlock:^(NSUInteger characterIndex, CGRect glyphBoundingRect, BOOL *stop) { XCTFail(@"Shouldn't be any text indexes to enumerate"); }]; - } @end