截断文本的多行NSAttributedString

我需要一个带有多行属性文本的UILabel subcass,支持链接,粗体样式等。我还需要用省略号截尾。 支持UILabels( TTTAttributedLabelOHAttributedLabelTTStyledTextLabel )中的属性文本的开源代码似乎都不支持多行文本的尾部截断。 有一个简单的方法来获得这个?

嗨,我是OHAttributedLabel的开发者。

有没有简单的方法来实现这一点(正如我在我的项目的github存储库中打开的相关问题所解释的),因为CoreText不提供这样的function。

这样做的唯一方法是使用CoreText对象(CTLine等)自己实现文本布局,而不是使用为您执行此操作(但不pipe理行截断)的CTFrameSetter 。 这个想法是build立所有的CTLine(依赖于它包含的NSAttributedString的字形和单词包装策略)并NSAttributedStringpipe理自己的省略号。

如果有人提出一个解决scheme来做到这一点,那么我真的很感激,因为看起来有点工作要做,而且你还必须pipe理一系列特殊/exception的情况(表情符号,奇怪的字体和不寻常的字形的字体,垂直alignment,考虑到省略号本身的大小,最后知道何时停止)。

所以随意挖掘并尝试实现自己的框架,这将是非常感激的!

也许我错过了一些东西,但最近怎么了? :

 NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithString:@"test"]; NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init]; style.lineBreakMode = NSLineBreakByTruncatingTail; [text addAttribute:NSParagraphStyleAttributeName value:style range:NSMakeRange(0, text.length)]; label.attributedText = text; 

这完美的作品,并会在最后添加一个省略号。

根据我在https://groups.google.com/forum/?fromgroups=#!topic/cocoa-unbound/Qin6gjYj7XU这里发现的内容,我想出了以下这个工作得很好。

 - (void)drawString:(CFAttributedStringRef)attString inRect:(CGRect)frameRect inContext: (CGContextRef)context { CGContextSaveGState(context); // Flip the coordinate system CGContextSetTextMatrix(context, CGAffineTransformIdentity); CGContextTranslateCTM(context, 0, self.bounds.size.height); CGContextScaleCTM(context, 1.0, -1.0); CGFloat height = self.frame.size.height; frameRect.origin.y = (height - frameRect.origin.y) - frameRect.size.height ; // Create a path to render text in // don't set any line break modes, etc, just let the frame draw as many full lines as will fit CGMutablePathRef framePath = CGPathCreateMutable(); CGPathAddRect(framePath, nil, frameRect); CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attString); CFRange fullStringRange = CFRangeMake(0, CFAttributedStringGetLength(attString)); CTFrameRef aFrame = CTFramesetterCreateFrame(framesetter, fullStringRange, framePath, NULL); CFRelease(framePath); CFArrayRef lines = CTFrameGetLines(aFrame); CFIndex count = CFArrayGetCount(lines); CGPoint *origins = malloc(sizeof(CGPoint)*count); CTFrameGetLineOrigins(aFrame, CFRangeMake(0, count), origins); // note that we only enumerate to count-1 in here-- we draw the last line separately for (CFIndex i = 0; i < count-1; i++) { // draw each line in the correct position as-is CGContextSetTextPosition(context, origins[i].x + frameRect.origin.x, origins[i].y + frameRect.origin.y); CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, i); CTLineDraw(line, context); } // truncate the last line before drawing it if (count) { CGPoint lastOrigin = origins[count-1]; CTLineRef lastLine = CFArrayGetValueAtIndex(lines, count-1); // truncation token is a CTLineRef itself CFRange effectiveRange; CFDictionaryRef stringAttrs = CFAttributedStringGetAttributes(attString, 0, &effectiveRange); CFAttributedStringRef truncationString = CFAttributedStringCreate(NULL, CFSTR("\u2026"), stringAttrs); CTLineRef truncationToken = CTLineCreateWithAttributedString(truncationString); CFRelease(truncationString); // now create the truncated line -- need to grab extra characters from the source string, // or else the system will see the line as already fitting within the given width and // will not truncate it. // range to cover everything from the start of lastLine to the end of the string CFRange rng = CFRangeMake(CTLineGetStringRange(lastLine).location, 0); rng.length = CFAttributedStringGetLength(attString) - rng.location; // substring with that range CFAttributedStringRef longString = CFAttributedStringCreateWithSubstring(NULL, attString, rng); // line for that string CTLineRef longLine = CTLineCreateWithAttributedString(longString); CFRelease(longString); CTLineRef truncated = CTLineCreateTruncatedLine(longLine, frameRect.size.width, kCTLineTruncationEnd, truncationToken); CFRelease(longLine); CFRelease(truncationToken); // if 'truncated' is NULL, then no truncation was required to fit it if (truncated == NULL) truncated = (CTLineRef)CFRetain(lastLine); // draw it at the same offset as the non-truncated version CGContextSetTextPosition(context, lastOrigin.x + frameRect.origin.x, lastOrigin.y + frameRect.origin.y); CTLineDraw(truncated, context); CFRelease(truncated); } free(origins); CGContextRestoreGState(context); 

}

我没有在所有情况下尝试过,但是像这样的东西可以用于截断:

 NSAttributedString *string = self.attributedString; CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetTextMatrix(context, CGAffineTransformIdentity); CFAttributedStringRef attributedString = (__bridge CFTypeRef)string; CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attributedString); CGPathRef path = CGPathCreateWithRect(self.bounds, NULL); CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL); BOOL needsTruncation = CTFrameGetVisibleStringRange(frame).length < string.length; CFArrayRef lines = CTFrameGetLines(frame); NSUInteger lineCount = CFArrayGetCount(lines); CGPoint *origins = malloc(sizeof(CGPoint) * lineCount); CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), origins); for (NSUInteger i = 0; i < lineCount; i++) { CTLineRef line = CFArrayGetValueAtIndex(lines, i); CGPoint point = origins[i]; CGContextSetTextPosition(context, point.x, point.y); BOOL truncate = (needsTruncation && (i == lineCount - 1)); if (!truncate) { CTLineDraw(line, context); } else { NSDictionary *attributes = [string attributesAtIndex:string.length-1 effectiveRange:NULL]; NSAttributedString *token = [[NSAttributedString alloc] initWithString:@"\u2026" attributes:attributes]; CFAttributedStringRef tokenRef = (__bridge CFAttributedStringRef)token; CTLineRef truncationToken = CTLineCreateWithAttributedString(tokenRef); double width = CTLineGetTypographicBounds(line, NULL, NULL, NULL) - CTLineGetTrailingWhitespaceWidth(line); CTLineRef truncatedLine = CTLineCreateTruncatedLine(line, width-1, kCTLineTruncationEnd, truncationToken); if (truncatedLine) { CTLineDraw(truncatedLine, context); } else { CTLineDraw(line, context); } if (truncationToken) { CFRelease(truncationToken); } if (truncatedLine) { CFRelease(truncatedLine); } } } free(origins); CGPathRelease(path); CFRelease(frame); CFRelease(framesetter); 

您可以使用后续代码来获得更简单的解决scheme。

  // last line. if (_limitToNumberOfLines && count == _numberOfLines-1) { // check if we reach end of text. if (lineRange.location + lineRange.length < [_text length]) { CFDictionaryRef dict = ( CFDictionaryRef)attributes; CFAttributedStringRef truncatedString = CFAttributedStringCreate(NULL, CFSTR("\u2026"), dict); CTLineRef token = CTLineCreateWithAttributedString(truncatedString); // not possible to display all text, add tail ellipsis. CTLineRef truncatedLine = CTLineCreateTruncatedLine(line, self.bounds.size.width - 20, kCTLineTruncationEnd, token); CFRelease(line); line = nil; line = truncatedLine; } } 

我在我的项目中使用MTLabel,这是我的项目非常好的解决scheme。

我将wbyoung的解决scheme集成到OHAttributedLabel的drawTextInRect:方法中,如果有人感兴趣:

 - (void)drawTextInRect:(CGRect)aRect { if (_attributedText) { CGContextRef ctx = UIGraphicsGetCurrentContext(); CGContextSaveGState(ctx); // flipping the context to draw core text // no need to flip our typographical bounds from now on CGContextConcatCTM(ctx, CGAffineTransformScale(CGAffineTransformMakeTranslation(0, self.bounds.size.height), 1.f, -1.f)); if (self.shadowColor) { CGContextSetShadowWithColor(ctx, self.shadowOffset, 0.0, self.shadowColor.CGColor); } [self recomputeLinksInTextIfNeeded]; NSAttributedString* attributedStringToDisplay = _attributedTextWithLinks; if (self.highlighted && self.highlightedTextColor != nil) { NSMutableAttributedString* mutAS = [attributedStringToDisplay mutableCopy]; [mutAS setTextColor:self.highlightedTextColor]; attributedStringToDisplay = mutAS; (void)MRC_AUTORELEASE(mutAS); } if (textFrame == NULL) { CFAttributedStringRef cfAttrStrWithLinks = (BRIDGE_CAST CFAttributedStringRef)attributedStringToDisplay; CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(cfAttrStrWithLinks); drawingRect = self.bounds; if (self.centerVertically || self.extendBottomToFit) { CGSize sz = CTFramesetterSuggestFrameSizeWithConstraints(framesetter,CFRangeMake(0,0),NULL,CGSizeMake(drawingRect.size.width,CGFLOAT_MAX),NULL); if (self.extendBottomToFit) { CGFloat delta = MAX(0.f , ceilf(sz.height - drawingRect.size.height))+ 10 /* Security margin */; drawingRect.origin.y -= delta; drawingRect.size.height += delta; } if (self.centerVertically) { drawingRect.origin.y -= (drawingRect.size.height - sz.height)/2; } } CGMutablePathRef path = CGPathCreateMutable(); CGPathAddRect(path, NULL, drawingRect); CFRange fullStringRange = CFRangeMake(0, CFAttributedStringGetLength(cfAttrStrWithLinks)); textFrame = CTFramesetterCreateFrame(framesetter,fullStringRange, path, NULL); CGPathRelease(path); CFRelease(framesetter); } // draw highlights for activeLink if (_activeLink) { [self drawActiveLinkHighlightForRect:drawingRect]; } BOOL hasLinkFillColorSelector = [self.delegate respondsToSelector:@selector(attributedLabel:fillColorForLink:underlineStyle:)]; if (hasLinkFillColorSelector) { [self drawInactiveLinkHighlightForRect:drawingRect]; } if (self.truncLastLine) { CFArrayRef lines = CTFrameGetLines(textFrame); CFIndex count = MIN(CFArrayGetCount(lines),floor(self.size.height/self.font.lineHeight)); CGPoint *origins = malloc(sizeof(CGPoint)*count); CTFrameGetLineOrigins(textFrame, CFRangeMake(0, count), origins); // note that we only enumerate to count-1 in here-- we draw the last line separately for (CFIndex i = 0; i < count-1; i++) { // draw each line in the correct position as-is CGContextSetTextPosition(ctx, origins[i].x + drawingRect.origin.x, origins[i].y + drawingRect.origin.y); CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, i); CTLineDraw(line, ctx); } // truncate the last line before drawing it if (count) { CGPoint lastOrigin = origins[count-1]; CTLineRef lastLine = CFArrayGetValueAtIndex(lines, count-1); // truncation token is a CTLineRef itself CFRange effectiveRange; CFDictionaryRef stringAttrs = CFAttributedStringGetAttributes((BRIDGE_CAST CFAttributedStringRef)_attributedTextWithLinks, 0, &effectiveRange); CFAttributedStringRef truncationString = CFAttributedStringCreate(NULL, CFSTR("\u2026"), stringAttrs); CTLineRef truncationToken = CTLineCreateWithAttributedString(truncationString); CFRelease(truncationString); // now create the truncated line -- need to grab extra characters from the source string, // or else the system will see the line as already fitting within the given width and // will not truncate it. // range to cover everything from the start of lastLine to the end of the string CFRange rng = CFRangeMake(CTLineGetStringRange(lastLine).location, 0); rng.length = CFAttributedStringGetLength((BRIDGE_CAST CFAttributedStringRef)_attributedTextWithLinks) - rng.location; // substring with that range CFAttributedStringRef longString = CFAttributedStringCreateWithSubstring(NULL, (BRIDGE_CAST CFAttributedStringRef)_attributedTextWithLinks, rng); // line for that string CTLineRef longLine = CTLineCreateWithAttributedString(longString); CFRelease(longString); CTLineRef truncated = CTLineCreateTruncatedLine(longLine, drawingRect.size.width, kCTLineTruncationEnd, truncationToken); CFRelease(longLine); CFRelease(truncationToken); // if 'truncated' is NULL, then no truncation was required to fit it if (truncated == NULL){ truncated = (CTLineRef)CFRetain(lastLine); } // draw it at the same offset as the non-truncated version CGContextSetTextPosition(ctx, lastOrigin.x + drawingRect.origin.x, lastOrigin.y + drawingRect.origin.y); CTLineDraw(truncated, ctx); CFRelease(truncated); } free(origins); } else{ CTFrameDraw(textFrame, ctx); } CGContextRestoreGState(ctx); } else { [super drawTextInRect:aRect]; } } 

多行垂直雕文与截断。 Swift3和Swift4版本。
添加:Xcode9.1 Swift4兼容性。 (使用块“#if swift(> = 4.0)”)

 class MultiLineVerticalGlyphWithTruncated: UIView, SimpleVerticalGlyphViewProtocol { var text:String! var font:UIFont! var isVertical:Bool! func setupProperties(text: String?, font:UIFont?, isVertical:Bool) { self.text = text ?? "" self.font = font ?? UIFont.systemFont(ofSize: UIFont.systemFontSize) self.isVertical = isVertical } override func draw(_ rect: CGRect) { if self.text == nil { return } // Create NSMutableAttributedString let attributed = NSMutableAttributedString(string: text) // if no ruby //let attributed = text.attributedStringWithRuby() // if with ruby, Please create custom method #if swift(>=4.0) attributed.addAttributes([ NSAttributedStringKey.font: font, NSAttributedStringKey.verticalGlyphForm: isVertical, ], range: NSMakeRange(0, attributed.length)) #else attributed.addAttributes([ kCTFontAttributeName as String: font, kCTVerticalFormsAttributeName as String: isVertical, ], range: NSMakeRange(0, attributed.length)) #endif drawContext(attributed, textDrawRect: rect, isVertical: isVertical) } } protocol SimpleVerticalGlyphViewProtocol { } extension SimpleVerticalGlyphViewProtocol { func drawContext(_ attributed:NSMutableAttributedString, textDrawRect:CGRect, isVertical:Bool) { guard let context = UIGraphicsGetCurrentContext() else { return } var path:CGPath if isVertical { context.rotate(by: .pi / 2) context.scaleBy(x: 1.0, y: -1.0) path = CGPath(rect: CGRect(x: textDrawRect.origin.y, y: textDrawRect.origin.x, width: textDrawRect.height, height: textDrawRect.width), transform: nil) } else { context.textMatrix = CGAffineTransform.identity context.translateBy(x: 0, y: textDrawRect.height) context.scaleBy(x: 1.0, y: -1.0) path = CGPath(rect: textDrawRect, transform: nil) } let framesetter = CTFramesetterCreateWithAttributedString(attributed) let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attributed.length), path, nil) // Check need for truncate tail if (CTFrameGetVisibleStringRange(frame).length as Int) < attributed.length { // Required truncate let linesNS: NSArray = CTFrameGetLines(frame) let linesAO: [AnyObject] = linesNS as [AnyObject] var lines: [CTLine] = linesAO as! [CTLine] let boundingBoxOfPath = path.boundingBoxOfPath let lastCTLine = lines.removeLast() let truncateString:CFAttributedString = CFAttributedStringCreate(nil, "\u{2026}" as CFString, CTFrameGetFrameAttributes(frame)) let truncateToken:CTLine = CTLineCreateWithAttributedString(truncateString) let lineWidth = CTLineGetTypographicBounds(lastCTLine, nil, nil, nil) let tokenWidth = CTLineGetTypographicBounds(truncateToken, nil, nil, nil) let widthTruncationBegins = lineWidth - tokenWidth if let truncatedLine = CTLineCreateTruncatedLine(lastCTLine, widthTruncationBegins, .end, truncateToken) { lines.append(truncatedLine) } var lineOrigins = Array<CGPoint>(repeating: CGPoint.zero, count: lines.count) CTFrameGetLineOrigins(frame, CFRange(location: 0, length: lines.count), &lineOrigins) for (index, line) in lines.enumerated() { context.textPosition = CGPoint(x: lineOrigins[index].x + boundingBoxOfPath.origin.x, y:lineOrigins[index].y + boundingBoxOfPath.origin.y) CTLineDraw(line, context) } } else { // Not required truncate CTFrameDraw(frame, context) } } } 

如何使用

 class ViewController: UIViewController { @IBOutlet weak var multiLineVerticalGlyphWithTruncated: MultiLineVerticalGlyphWithTruncated! // UIView let font:UIFont = UIFont(name: "HiraMinProN-W3", size: 17.0) ?? UIFont.systemFont(ofSize: 17.0) override func viewDidLoad() { super.viewDidLoad() let text = "iOS 11 sets a new standard for what is already the world's most advanced mobile operating system. It makes iPhone better than before. It makes iPad more capable than ever. And now it opens up both to amazing possibilities for augmented reality in games and apps. With iOS 11, iPhone and iPad are the most powerful, personal, and intelligent devices they've ever been." // If check for Japanese // let text = "すでに世界で最も先進的なモバイルオペレーティングシステムであるiOSに、iOS 11が新たな基準を打ち立てます。iPhoneは今まで以上に優れたものになり、iPadはかつてないほどの能力を手に入れます。さらにこれからはどちらのデバイスにも、ゲームやアプリケーションの拡張現実のための驚くような可能性が広がります。iOS 11を搭載するiPhoneとiPadは、間違いなくこれまでで最もパワフルで、最もパーソナルで、最も賢いデバイスです。" // if not vertical text, isVertical = false multiLineVerticalGlyphWithTruncated.setupProperties(text: text, font: font, isVertical: true) } } どちらのデバイスにもclass ViewController: UIViewController { @IBOutlet weak var multiLineVerticalGlyphWithTruncated: MultiLineVerticalGlyphWithTruncated! // UIView let font:UIFont = UIFont(name: "HiraMinProN-W3", size: 17.0) ?? UIFont.systemFont(ofSize: 17.0) override func viewDidLoad() { super.viewDidLoad() let text = "iOS 11 sets a new standard for what is already the world's most advanced mobile operating system. It makes iPhone better than before. It makes iPad more capable than ever. And now it opens up both to amazing possibilities for augmented reality in games and apps. With iOS 11, iPhone and iPad are the most powerful, personal, and intelligent devices they've ever been." // If check for Japanese // let text = "すでに世界で最も先進的なモバイルオペレーティングシステムであるiOSに、iOS 11が新たな基準を打ち立てます。iPhoneは今まで以上に優れたものになり、iPadはかつてないほどの能力を手に入れます。さらにこれからはどちらのデバイスにも、ゲームやアプリケーションの拡張現実のための驚くような可能性が広がります。iOS 11を搭載するiPhoneとiPadは、間違いなくこれまでで最もパワフルで、最もパーソナルで、最も賢いデバイスです。" // if not vertical text, isVertical = false multiLineVerticalGlyphWithTruncated.setupProperties(text: text, font: font, isVertical: true) } }ーションの拡张现実のための惊くような可能性が広がりますclass ViewController: UIViewController { @IBOutlet weak var multiLineVerticalGlyphWithTruncated: MultiLineVerticalGlyphWithTruncated! // UIView let font:UIFont = UIFont(name: "HiraMinProN-W3", size: 17.0) ?? UIFont.systemFont(ofSize: 17.0) override func viewDidLoad() { super.viewDidLoad() let text = "iOS 11 sets a new standard for what is already the world's most advanced mobile operating system. It makes iPhone better than before. It makes iPad more capable than ever. And now it opens up both to amazing possibilities for augmented reality in games and apps. With iOS 11, iPhone and iPad are the most powerful, personal, and intelligent devices they've ever been." // If check for Japanese // let text = "すでに世界で最も先進的なモバイルオペレーティングシステムであるiOSに、iOS 11が新たな基準を打ち立てます。iPhoneは今まで以上に優れたものになり、iPadはかつてないほどの能力を手に入れます。さらにこれからはどちらのデバイスにも、ゲームやアプリケーションの拡張現実のための驚くような可能性が広がります。iOS 11を搭載するiPhoneとiPadは、間違いなくこれまでで最もパワフルで、最もパーソナルで、最も賢いデバイスです。" // if not vertical text, isVertical = false multiLineVerticalGlyphWithTruncated.setupProperties(text: text, font: font, isVertical: true) } }でで最もパワフルclass ViewController: UIViewController { @IBOutlet weak var multiLineVerticalGlyphWithTruncated: MultiLineVerticalGlyphWithTruncated! // UIView let font:UIFont = UIFont(name: "HiraMinProN-W3", size: 17.0) ?? UIFont.systemFont(ofSize: 17.0) override func viewDidLoad() { super.viewDidLoad() let text = "iOS 11 sets a new standard for what is already the world's most advanced mobile operating system. It makes iPhone better than before. It makes iPad more capable than ever. And now it opens up both to amazing possibilities for augmented reality in games and apps. With iOS 11, iPhone and iPad are the most powerful, personal, and intelligent devices they've ever been." // If check for Japanese // let text = "すでに世界で最も先進的なモバイルオペレーティングシステムであるiOSに、iOS 11が新たな基準を打ち立てます。iPhoneは今まで以上に優れたものになり、iPadはかつてないほどの能力を手に入れます。さらにこれからはどちらのデバイスにも、ゲームやアプリケーションの拡張現実のための驚くような可能性が広がります。iOS 11を搭載するiPhoneとiPadは、間違いなくこれまでで最もパワフルで、最もパーソナルで、最も賢いデバイスです。" // if not vertical text, isVertical = false multiLineVerticalGlyphWithTruncated.setupProperties(text: text, font: font, isVertical: true) } }です class ViewController: UIViewController { @IBOutlet weak var multiLineVerticalGlyphWithTruncated: MultiLineVerticalGlyphWithTruncated! // UIView let font:UIFont = UIFont(name: "HiraMinProN-W3", size: 17.0) ?? UIFont.systemFont(ofSize: 17.0) override func viewDidLoad() { super.viewDidLoad() let text = "iOS 11 sets a new standard for what is already the world's most advanced mobile operating system. It makes iPhone better than before. It makes iPad more capable than ever. And now it opens up both to amazing possibilities for augmented reality in games and apps. With iOS 11, iPhone and iPad are the most powerful, personal, and intelligent devices they've ever been." // If check for Japanese // let text = "すでに世界で最も先進的なモバイルオペレーティングシステムであるiOSに、iOS 11が新たな基準を打ち立てます。iPhoneは今まで以上に優れたものになり、iPadはかつてないほどの能力を手に入れます。さらにこれからはどちらのデバイスにも、ゲームやアプリケーションの拡張現実のための驚くような可能性が広がります。iOS 11を搭載するiPhoneとiPadは、間違いなくこれまでで最もパワフルで、最もパーソナルで、最も賢いデバイスです。" // if not vertical text, isVertical = false multiLineVerticalGlyphWithTruncated.setupProperties(text: text, font: font, isVertical: true) } } 

我用作示例MTLabel 。 它允许pipe理行高。 我需要的绘制方法,所以我只是把我不需要的东西大部分。 这个方法允许我用拖尾截断绘制多行文本。

 CGRect CTLineGetTypographicBoundsAsRect(CTLineRef line, CGPoint lineOrigin) { CGFloat ascent = 0; CGFloat descent = 0; CGFloat leading = 0; CGFloat width = CTLineGetTypographicBounds(line, &ascent, &descent, &leading); CGFloat height = ascent + descent; return CGRectMake(lineOrigin.x, lineOrigin.y - descent, width, height); } - (void)drawText:(NSString*) text InRect:(CGRect)rect withFont:(UIFont*)aFont inContext:(CGContextRef)context { if (!text) { return; } BOOL _limitToNumberOfLines = YES; int _numberOfLines = 2; float _lineHeight = 22; //Create a CoreText font object with name and size from the UIKit one CTFontRef font = CTFontCreateWithName((CFStringRef)aFont.fontName , aFont.pointSize, NULL); //Setup the attributes dictionary with font and color NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys: (id)font, (id)kCTFontAttributeName, [UIColor lightGrayColor].CGColor, kCTForegroundColorAttributeName, nil]; NSAttributedString *attributedString = [[[NSAttributedString alloc] initWithString:text attributes:attributes] autorelease]; CFRelease(font); //Create a TypeSetter object with the attributed text created earlier on CTTypesetterRef typeSetter = CTTypesetterCreateWithAttributedString((CFAttributedStringRef)attributedString); //Start drawing from the upper side of view (the context is flipped, so we need to grab the height to do so) CGFloat y = self.bounds.origin.y + self.bounds.size.height - rect.origin.y - aFont.ascender; BOOL shouldDrawAlong = YES; int count = 0; CFIndex currentIndex = 0; float _textHeight = 0; CGContextSaveGState(context); CGContextSetTextMatrix(context, CGAffineTransformIdentity); CGContextTranslateCTM(context, 0, self.bounds.size.height); CGContextScaleCTM(context, 1.0, -1.0); //Start drawing lines until we run out of text while (shouldDrawAlong) { //Get CoreText to suggest a proper place to place the line break CFIndex lineLength = CTTypesetterSuggestLineBreak(typeSetter, currentIndex, rect.size.width); //Create a new line with from current index to line-break index CFRange lineRange = CFRangeMake(currentIndex, lineLength); CTLineRef line = CTTypesetterCreateLine(typeSetter, lineRange); //Check to see if our index didn't exceed the text, and if should limit to number of lines if (currentIndex + lineLength >= [text length]) { shouldDrawAlong = NO; } else { if (!(_limitToNumberOfLines && count < _numberOfLines-1)) { int i = 0; if ([[[text substringWithRange:NSMakeRange(currentIndex, lineLength)] stringByAppendingString:@"…"] sizeWithFont:aFont].width > rect.size.width) { i--; while ([[[text substringWithRange:NSMakeRange(currentIndex, lineLength + i)] stringByAppendingString:@"…"] sizeWithFont:aFont].width > rect.size.width) { i--; } } else { i++; while ([[[text substringWithRange:NSMakeRange(currentIndex, lineLength + i)] stringByAppendingString:@"…"] sizeWithFont:aFont].width < rect.size.width) { i++; } i--; } attributedString = [[[NSAttributedString alloc] initWithString:[[text substringWithRange:NSMakeRange(currentIndex, lineLength + i)] stringByAppendingString:@"…"] attributes:attributes] autorelease]; CFRelease(typeSetter); typeSetter = CTTypesetterCreateWithAttributedString((CFAttributedStringRef)attributedString); CFRelease(line); CFRange lineRange = CFRangeMake(0, 0); line = CTTypesetterCreateLine(typeSetter, lineRange); shouldDrawAlong = NO; } } CGFloat x = rect.origin.x; //Setup the line position CGContextSetTextPosition(context, x, y); CTLineDraw(line, context); count++; CFRelease(line); y -= _lineHeight; currentIndex += lineLength; _textHeight += _lineHeight; } CFRelease(typeSetter); CGContextRestoreGState(context); }