2011-09-30 31 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) 
    } 
}