Multi-line NSAttributedString with truncated text

后端 未结 9 2043
情书的邮戳
情书的邮戳 2020-12-23 14:55

I need a UILabel subcass with multiline attributed text with support for links, bold styles, etc. I also need tail truncation with an ellipsis. None of the open source co

9条回答
  •  无人及你
    2020-12-23 15:30

    Based on what I found here and over at https://groups.google.com/forum/?fromgroups=#!topic/cocoa-unbound/Qin6gjYj7XU, I came up with the following which works very well.

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

    }

提交回复
热议问题