9 题: 带有截断文本的多行NSAttributedString

在...创建的问题 Thu, Nov 3, 2016 12:00 AM

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

    
22
9答案                              9 跨度>                         

您好我是 OHAttributedLabel 的开发者。

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

执行此操作的唯一方法是使用CoreText对象(CTLine等)自行实现文本布局,而不是使用为您执行此操作的CTFrameSetter(但不管理行截断)。我们的想法是建立所有的CTLine来排列它们(取决于它包含的NSAttributedString中的字形和自动换行策略),并自己管理省略号。

我真的很感激,如果有人提出解决方案来做这项工作,因为它似乎需要做一些工作,你必须管理一系列特殊/不寻常的案例(表情符号案例,具有奇怪指标的字体和不寻常的字形,垂直对齐,考虑到省略号本身的大小,知道何时停止。)

所以请随意挖掘并尝试自己实现框架的构图,真的很感激!

    
12
2011-12-02 10:14:09Z
  1. 非常感谢您的反应,并确认这是我所想的情况。简单而天真的解决方案是否存在问题,可以计算是否需要截断,然后只需从属性字符串中删除1或2个字符并在其位置添加省略号?
    2011-09-30 15:47:36Z
  2. 即使您必须确定要移除哪些字符(文本被转换为什么偏移量 - 这就是easiers部分)以及多少(这是更复杂)取决于截断点处的字体和大小(纯文本中的3个字母'i'不够,但粗体中的3'w'将太多...),特别是如果中间发生的则风格的变化(字体,样式,大小......)呢!另外“...”省略号单个字符的大小也不一样,具体取决于字体...,所以即使这是可能的,也要小心所有那些棘手的案例
    2011-09-30 16:28:10Z
  3. 我还没试过这个,但我也注意到在手册行的CoreText文档中打破了CTTypesetterSuggestLineBreak函数,它会在给定宽度的属性字符串上建议换行符。通过使用相同的属性字符串计算省略号的宽度,可以在最后一个CTLine上计算此宽度,看起来您的大多数特殊情况都可以正确处理。
    2011-10-06 11:19:10Z
  4. 提示,看起来很有希望。 注意:如果您有一个github帐户,请不要犹豫,对相关问题发表评论(或做一个分叉尝试一些代码,然后发送拉动请求)
    2011-10-06 12:02:17Z
  5. 醇>

也许我错过了什么,但是错了什么? :

 
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;

这非常有效,并且会在末尾添加省略号。

    
36
2015-02-18 15:27:37Z
  1. 已验证。完美适用,iOS 8.3在这里。
    2015-04-16 15:13:44Z
  2. 这应该是正确的答案。在iOS6之前,UILabel不支持属性字符串,这就是为什么这曾经是一个棘手的问题。
    2015-11-24 21:21:55Z
  3. @ Toydor我使用tttattributed标签进行截断。但是如果假设\n是在文本中,那么截断的链接没有被点击。所以有任何方式,以便我可以在字符串的末尾追加我的自定义截断标记,如果它被截断。
    2016-06-09 14:27:29Z
  4. 问题lineBreakMode不允许设置除...之外的自定义省略号:(
    2016-12-28 10:24:24Z
  5. 它不适用于xcode 10 swift 4.2
    2018-10-23 10:39:59Z
  6. 醇>

基于我在 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);

}

    
9
2013-02-07 16:30:58Z
  1. 优秀的解决方案!
    2013-06-24 21:59:26Z
  2. 你能解释一下,哪个类是你的子类?
    2014-01-15 15:13:21Z
  3. @ naughton这段代码正在为我泄漏...... CTFrameRef是否也应该被释放?
    2014-06-30 20:49:16Z
  4. @ naughton在CFRelease(aFrame); CFRelease(framesetter);结束后添加了free(origins)结果......没有泄漏!
    2014-06-30 20:56:03Z
  5. 我们可以将Image添加到truncationToken字符串吗?我的意思是属性字符串与NSTextAttachment。?
    2014-09-08 12:15:51Z
  6. 醇>

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

 
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);
    
3
2013-01-30 20:07:23Z
  1. 这似乎有效,但文字是颠倒的。为了防止这种情况,需要在CGContextSetTextMatrix(context, CGAffineTransformIdentity);CGContextTranslateCTM(context, 0.0f, rect.size.height); CGContextScaleCTM(context, 1.0f, -1.0f);
    之后添加以下代码行
    2015-05-31 09:45:34Z
  2. 醇>

您可以使用以下代码来获得更简单的解决方案。

 
    // 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,这对我的项目来说是一个非常好的解决方案。

    
1
2012-04-16 18:12:20Z

多行垂直字形与截断。 Swift3和Swift4版本。
添加:Xcode9.1 Swift4兼容性。 (我们e块“#if swift(&gt; = 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)
    }
}
    
1
2017-11-19 10:43:58Z

如果有兴趣的话,我将wbyoung的解决方案集成到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];
        }
}
    
0
2014-03-20 10:09:55Z

这是@ Toydor回答的Swift 5版本:

 
let attributedString = NSMutableAttributedString(string: "my String")    
let style: NSMutableParagraphStyle = NSMutableParagraphStyle()
style.lineBreakMode = .byTruncatingTail
attributedString.addAttribute(NSAttributedString.Key.paragraphStyle,
                                      value: style,
                                      range: NSMakeRange(0, attributedString.length))
    
0
2019-05-12 08:48:03Z

我用作示例 MTLabel 。它允许管理线高。 我完全需要绘制方法,所以我只收拾了大部分我不需要的东西。 这种方法允许我在rect中用尾部截断绘制多行文本。

 
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);

}
    
- 1
2012-03-05 16:31:18Z
来源放置 这里