Multi-line NSAttributedString with truncated text

后端 未结 9 2026
情书的邮戳
情书的邮戳 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:29

    Multi Line Vertical Glyph With Truncated. Swift3 and Swift4 version.
    Add: Xcode9.1 Swift4 Compatibility. ( use block "#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(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)
            }
        }
    }
    

    How to use

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

提交回复
热议问题