本文的主要內容是使用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]; }