2011-09-30 111 views
21

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

回答

12

嗨,我是OHAttributedLabel的开发者。

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

要做到这一点的唯一方法是使用CoreText对象(CTLine等)自己实现文本布局,而不是使用CTFrameSetter这样做(但不管用于管理行截断)。这个想法是建立所有的CTLine,根据它包含的NSAttributedString中的字形和单词包装策略依次放置它们并自己管理最后的省略号。

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

因此,随时挖掘并尝试实现自己的线框架,这将是真正感激!

+0

感谢这么多的响应和确认,这是因为我原以为这种情况。是否有一个简单而幼稚的解决方案会计算是否需要截断,然后从属性字符串中删除1或2个字符并在其位置添加省略号? –

+0

即使您必须确定要删除哪些字符(文本在哪个偏移量处指定 - 这就是easiers部分)以及多少(这更复杂),这取决于截断点处的字体和大小(即, 3个字母'我'在纯文本是不够的,但3'W'粗体将是太多了...),特别是如果trunction发生在风格(字体,风格,大小,...)太!此外,“...”省略号单个字符的大小也不一样,具体取决于字体...因此,即使这是可能的,小心所有那些棘手的情况下 – AliSoftware

+1

我没有尝试过,但我也注意到在CoreText手动打破'CTTypesetterSuggestLineBreak'函数的文档,它将为给定宽度的属性字符串提示换行符。通过计算具有相同属性字符串的省略号的宽度,可以在最后的CTLine上计算这个宽度,并且似乎大部分上述特殊情况都可以正确处理。 –

-1

我用作样本MTLabel。它允许管理行高。 我完全需要绘制方法,所以我只是放弃了大部分我不需要的东西。 此方法允许我绘制直线尾部截断的多线文本。

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

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

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

3

我还没有在所有的情况下尝试这样做,但像这样能为截断工作:

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

这似乎是有效的,但文本是向上绘制的-下。为了防止这种情况,需要在CGContextSetTextMatrix(context,CGAffineTransformIdentity);': 'CGContextTranslateCTM(context,0.0f,rect.size.height)之后添加以下代码行。 CGContextScaleCTM(context,1.0f,-1.0f);' – Julia

8

基于什么我发现这里上并且在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); 

}

+0

优秀的解决方案! – mydogisbox

+2

你能解释一下,你是继承了哪一类? – asdf

+0

@naughton此代码泄漏给我......应该发布'CTFrameRef'吗? – mga

0

我综合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]; 
     } 
} 
30

也许我失去了一些东西,但什么错? :

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; 

这完美的作品,将一个省略号添加到末尾。

+0

已验证。完美的工作,这里的iOS 8.3。 –

+2

这应该是现在正确的答案。在iOS6之前,UILabel不支持属性字符串,这就是为什么这是一个难题。 – sabajt

+0

@Toydor我正在使用tttattributed标签进行截断。但是,如果假设\ n在文本中存在,则截断的链接不会被单击。因此,如果它被截断,那么我可以在字符串末尾追加自定义截断的标记。 – Aditya

0

多行垂直雕文与截断。 Swift3和Swift4版本。
添加:Xcode9.1 Swift4兼容性。 (使用块“#如果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) 
    } 
}