本文的主要内容是使用CoreText如何进行行数的限制,以及设置了行数限制末尾的内容被截断了怎么设置截断的标识。此外,还有如何设置自定义的截断标识字符串(好比“显示更多”)、设置自定义截断标识字符串的点击事件等的相关讨论git
其它文章:
CoreText入门(一)-文本绘制
CoreText入门(二)-绘制图片
CoreText进阶(三)-事件处理
CoreText进阶(四)-文字行数限制和显示更多
CoreText进阶(五)- 文字排版样式和效果
CoreText进阶(六)-内容大小计算和自动布局
CoreText进阶(七)-添加自定义View和对其app
Demo:CoreTextDemo布局
效果图:atom
默认的截断标识和自定义的截断标识符效果图
 .net
点击查看更多以后的效果图code
orm
为了能够设置显示的行数以及截断的标识字符串,YTDrawView
类提供了三个属性,外部能够经过设置参数的方式来设置行数和截断的标识字符串,而且能够设置点击事件对象
@property (nonatomic, assign) NSInteger numberOfLines; ///< 行数 @property (nonatomic, strong) NSAttributedString *truncationToken;///<截断的标识字符串,默认是"..." @property (nonatomic, copy) ClickActionHandler truncationActionHandler;///<截断的标识字符串点击事件
使用的示例代码:blog
CGRect frame = CGRectMake(0, 100, self.view.bounds.size.width, 100); YTDrawView *textDrawView = [[YTDrawView alloc] initWithFrame:frame]; textDrawView.backgroundColor = [UIColor whiteColor]; textDrawView.numberOfLines = 3; [textDrawView addString:@"这是一个最好的时代,也是一个最坏的时代;这是明智的时代,这是愚昧的时代;这是信任的纪元,这是怀疑的纪元;这是光明的季节,这是黑暗的季节;这是但愿的春日,这是失望的冬日;咱们面前应有尽有,咱们面前一无全部;咱们都将直上天堂,咱们都将直下地狱。" attributes:self.defaultTextAttributes clickActionHandler:^(id obj) { }]; [self.view addSubview:textDrawView]; NSAttributedString * truncationToken = [[NSAttributedString alloc] initWithString:@"查看更多" attributes:[self truncationTextAttributes]]; frame = CGRectMake(0, 220, self.view.bounds.size.width, 100); textDrawView = [[YTDrawView alloc] initWithFrame:frame]; textDrawView.backgroundColor = [UIColor whiteColor]; textDrawView.numberOfLines = 2; textDrawView.truncationToken = truncationToken; [textDrawView addString:@"这是一个最好的时代,也是一个最坏的时代;这是明智的时代,这是愚昧的时代;这是信任的纪元,这是怀疑的纪元;这是光明的季节,这是黑暗的季节;这是但愿的春日,这是失望的冬日;咱们面前应有尽有,咱们面前一无全部;咱们都将直上天堂,咱们都将直下地狱。" attributes:self.defaultTextAttributes clickActionHandler:^(id obj) { }]; __weak typeof(textDrawView) weakDrawView = textDrawView; textDrawView.truncationActionHandler = ^(id obj) { NSLog(@"点击查看更多"); weakDrawView.numberOfLines = 0; }; [self.view addSubview:textDrawView];
主要的有如下几个步骤:token
CTFrameDraw
)便可,有行数限制继续下一步CTLineDraw
,而且须要使用CGContextSetTextPosition
方法设置绘制文本的位置)CTLineCreateTruncatedLine
建立最后一行显示的内容,返回CTLine对象truncationActionHandler
截断的标识字符串点击事件,须要把位置信息进行保存,用于后面的事件处理CGContextSetTextPosition
在使用CTLineDraw
绘制一个CTline
以前须要设置CTline
绘制的位置,这个值可使用CTFrameGetLineOrigins
方法来获取CTLineDraw
和CTFrameDraw
相似,不过是以行为单位进行绘制,灵活性更高,在有行数显示须要添加特殊的截断标识的场景须要使用这个方法才能知足要求CTLineCreateTruncatedLine
建立一个带有特殊截断标识的CTLine
对象数据的处理依然放在YTRichContentData
类中进行,calculateTruncatedLinesWithBounds
就是以上分析的步骤的代码实现,关键的步骤在代码中都有注释
- (void)calculateTruncatedLinesWithBounds:(CGRect)bounds { // 清除旧的数据 [self.truncations removeAllObjects]; // 获取最终须要绘制的文本行数 CFIndex numberOfLinesToDraw = [self numberOfLinesToDrawWithCTFrame:self.ctFrame]; if (numberOfLinesToDraw <= 0) { self.drawMode = YTDrawModeFrame; } else { self.drawMode = YTDrawModeLines; NSArray *lines = (NSArray *)CTFrameGetLines(self.ctFrame); CGPoint lineOrigins[numberOfLinesToDraw]; CTFrameGetLineOrigins(self.ctFrame, CFRangeMake(0, numberOfLinesToDraw), lineOrigins); for (int lineIndex = 0; lineIndex < numberOfLinesToDraw; lineIndex ++) { CTLineRef line = (__bridge CTLineRef)(lines[lineIndex]); CFRange range = CTLineGetStringRange(line); // 判断最后一行是否须要显示【截断标识字符串(...)】 if ( lineIndex == numberOfLinesToDraw - 1 && range.location + range.length < [self attributeStringToDraw].length) { // 建立【截断标识字符串(...)】 NSAttributedString *tokenString = nil; if (_truncationToken) { tokenString = _truncationToken; } else { NSUInteger truncationAttributePosition = range.location + range.length - 1; NSDictionary *tokenAttributes = [[self attributeStringToDraw] attributesAtIndex:truncationAttributePosition effectiveRange:NULL]; tokenString = [[NSAttributedString alloc] initWithString:@"\u2026" attributes:tokenAttributes]; } // 计算【截断标识字符串(...)】的长度 CGSize tokenSize = [tokenString boundingRectWithSize:CGSizeMake(MAXFLOAT, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin context:NULL].size; CGFloat tokenWidth = tokenSize.width; CTLineRef truncationTokenLine = CTLineCreateWithAttributedString((CFAttributedStringRef)tokenString); // 根据【截断标识字符串(...)】的长度,计算【须要截断字符串】的最后一个字符的位置,把该位置以后的字符从【须要截断字符串】中移除,留出【截断标识字符串(...)】的位置 CFIndex truncationEndIndex = CTLineGetStringIndexForPosition(line, CGPointMake(bounds.size.width - tokenWidth, 0)); CGFloat length = range.location + range.length - truncationEndIndex; // 把【截断标识字符串(...)】添加到【须要截断字符串】后面 NSMutableAttributedString *truncationString = [[[self attributeStringToDraw] attributedSubstringFromRange:NSMakeRange(range.location, range.length)] mutableCopy]; if (length < truncationString.length) { [truncationString deleteCharactersInRange:NSMakeRange(truncationString.length - length, length)]; [truncationString appendAttributedString:tokenString]; } // 使用`CTLineCreateTruncatedLine`方法建立含有【截断标识字符串(...)】的`CTLine`对象 CTLineRef truncationLine = CTLineCreateWithAttributedString((CFAttributedStringRef)truncationString); CTLineTruncationType truncationType = kCTLineTruncationEnd; CTLineRef lastLine = CTLineCreateTruncatedLine(truncationLine, bounds.size.width, truncationType, truncationTokenLine); // 添加truncation的位置信息 NSArray *runs = (NSArray *)CTLineGetGlyphRuns(line); if (runs.count > 0 && self.truncationActionHandler) { CTRunRef run = (__bridge CTRunRef)runs.lastObject; CGFloat ascent; CGFloat desent; // 能够直接从metaData获取到图片的宽度和高度信息 CGFloat width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &desent, NULL); CGFloat height = ascent + desent; YTTruncationItem* truncationItem = [YTTruncationItem new]; CGRect truncationFrame = CGRectMake(width - tokenWidth, bounds.size.height - lineOrigins[lineIndex].y - height, tokenSize.width, tokenSize.height); [truncationItem addFrame:truncationFrame]; truncationItem.clickActionHandler = self.truncationActionHandler; [self.truncations addObject:truncationItem]; } YTCTLine *ytLine = [YTCTLine new]; ytLine.ctLine = lastLine; ytLine.position = CGPointMake(lineOrigins[lineIndex].x, lineOrigins[lineIndex].y); [self.linesToDraw addObject:ytLine]; CFRelease(truncationTokenLine); CFRelease(truncationLine); } else { YTCTLine *ytLine = [YTCTLine new]; ytLine.ctLine = line; ytLine.position = CGPointMake(lineOrigins[lineIndex].x, lineOrigins[lineIndex].y); [self.linesToDraw addObject:ytLine]; } } } }
点击了“显示更多”,调用的是YTDrawView
类的setNumberOfLines
方法,方法的处理很简单只是更新YTRichContentData
类的numberOfLines
属性,调用setNeedsDisplay
方法请求YTDrawView
类进行从新绘制
- (void)setNumberOfLines:(NSInteger)numberOfLines { self.data.numberOfLines = numberOfLines; [self setNeedsDisplay]; }
YTDrawView
类会调用drawRect
方法,drawRect
方法会从新处理数据和进行绘制
- (void)drawRect:(CGRect)rect { [super drawRect:rect]; CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetTextMatrix(context, CGAffineTransformIdentity); CGContextTranslateCTM(context, 0, self.bounds.size.height); CGContextScaleCTM(context, 1, -1); // 处理数据 [self.data composeDataWithBounds:self.bounds]; // 绘制文字 [self drawTextInContext:context]; // 绘制图片 [self drawImagesInContext:context]; }