CATextLayer使用截断和段落样式渲染AttributedString

时间:2018-01-28 02:39:39

标签: ios swift4

我正在尝试创建一个具有动画属性的自定义标签,因此我决定使用CATextLayer而不是直接转到CoreText ..

我想出了以下代码(我用操场来测试):

//: A UIKit based Playground for presenting user interface

import UIKit
import PlaygroundSupport


public extension CGRect {
    public static func centerRect(_ rectToCenter: CGRect, in rect: CGRect) -> CGRect {
        return CGRect(x: rect.origin.x + ((rect.width - rectToCenter.width) / 2.0),
                      y: rect.origin.y + ((rect.height - rectToCenter.height) / 2.0),
                      width: rectToCenter.width,
                      height: rectToCenter.height)
    }
}

class MyLabel : UIView {
    private let textLayer = CATextLayer()
    public var textColor: UIColor = UIColor.black
    public var font: UIFont = UIFont.systemFont(ofSize: 17.0)
    private var _lineBreak: NSLineBreakMode = .byTruncatingTail


    init() {
        super.init(frame: .zero)
        self.text = nil
        self.textAlignment = .natural
        self.lineBreakMode = .byTruncatingTail
        self.textLayer.isWrapped = true

        self.layer.addSublayer(self.textLayer)
    }

    @available(*, unavailable)
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }


    //Update the CATextLayer's attributed string when this property is set..
    public var text: String? {

        //Getter just returns the CATextLayer's string
        get {
            if let attrString = self.textLayer.string as? NSAttributedString {
                return attrString.string
            }

            return self.textLayer.string as? String
        }

        //Setter creates an attributed string with paragraph style..
        //Font and colour..
        set {
            if let value = newValue {
                let paragraphStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
                paragraphStyle.alignment = self.textAlignment
                paragraphStyle.lineBreakMode = self.lineBreakMode

                self.textLayer.string = NSMutableAttributedString(string: value, attributes: [
                    .foregroundColor: self.textColor,
                    .font: self.font])
            }
            else {
                self.textLayer.string = nil
            }
        }
    }

    //Convert NSTextAlignment to kCAAlignment String for CATextLayer
    public var textAlignment: NSTextAlignment {
        get {
            switch self.textLayer.alignmentMode {
            case kCAAlignmentLeft:
                return .left

            case kCAAlignmentCenter:
                return .center

            case kCAAlignmentRight:
                return .right

            case kCAAlignmentJustified:
                return .justified

            default:
                return .natural
            }
        }

        set {
            switch newValue {
            case .left:
                self.textLayer.alignmentMode = kCAAlignmentLeft

            case .center:
                self.textLayer.alignmentMode = kCAAlignmentCenter

            case .right:
                self.textLayer.alignmentMode = kCAAlignmentRight

            case .justified:
                self.textLayer.alignmentMode = kCAAlignmentJustified

            default:
                self.textLayer.alignmentMode = kCAAlignmentNatural
            }
        }
    }

    //Convert NSLineBreakMode to kCAAlignmentMode String
    public var lineBreakMode: NSLineBreakMode {
        get {
            return _lineBreak
        }

        set {
            _lineBreak = newValue

            switch newValue {
            case .byWordWrapping:
                self.textLayer.isWrapped = true
                self.textLayer.truncationMode = kCATruncationNone

            case .byCharWrapping:
                self.textLayer.isWrapped = true
                self.textLayer.truncationMode = kCATruncationNone

            case .byClipping:
                self.textLayer.isWrapped = false
                self.textLayer.truncationMode = kCATruncationNone

            case .byTruncatingHead:
                self.textLayer.truncationMode = kCATruncationStart

            case .byTruncatingMiddle:
                self.textLayer.truncationMode = kCATruncationMiddle

            case .byTruncatingTail:
                self.textLayer.truncationMode = kCATruncationEnd
            }
        }
    }


    //Override layoutSubviews to render the CATextLayer..
    internal override func layoutSubviews() {
        super.layoutSubviews()

        //Calculate attributed string size..
        let string = self.textLayer.string as! NSAttributedString
        var rect = string.boundingRect(with: self.bounds.size, options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil)

        //If you change dy to 1.0, Text will render weirdly!
        rect = rect.insetBy(dx: 0.0, dy: 0.0)

        //Render the textLayer by centering it in its parent..
        self.textLayer.contentsScale = UIScreen.main.scale
        self.textLayer.rasterizationScale = UIScreen.main.scale
        self.textLayer.frame = CGRect.centerRect(rect, in: self.bounds)
        self.textLayer.backgroundColor = UIColor.lightGray.cgColor
    }

    //Somehow always returns 21.. ={
    //Not used right now because it doesn't work.. at all..
    private func sizeOfTextThatFits(size: CGSize) -> CGSize {
        if let string = self.textLayer.string as? NSAttributedString {
            let path = CGMutablePath()
            path.addRect(CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))

            let frameSetter = CTFramesetterCreateWithAttributedString(string as CFAttributedString)
            let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, nil)
            let lines = CTFrameGetLines(frame) as NSArray

            var lineWidth: CGFloat = 0.0
            var yOffset: CGFloat = 0.0

            for line in lines {
                let ctLine = line as! CTLine
                var ascent: CGFloat = 0.0
                var descent: CGFloat = 0.0
                var leading: CGFloat = 0.0
                lineWidth = CGFloat(max(CTLineGetTypographicBounds(ctLine, &ascent, &descent, &leading), Double(lineWidth)))
                yOffset += ascent + descent + leading;
            }
            return CGSize(width: lineWidth, height: yOffset)
        }
        return .zero
    }
}


class MyViewController : UIViewController {
    override func loadView() {
        let view = UIView()
        view.backgroundColor = .white

        let label = MyLabel()
        label.backgroundColor = UIColor.red
        label.frame = CGRect(x: 150, y: 200, width: 200, height: 200)
        label.text = "Hello World! Hello World! Hello World! Hello World! Hello World! Hello World!Hello World! Hello World! Hello World! Hello World! Hello World! Hello World!Hello World! Hello World! Hello World! Hello World! Hello World! Hello World!Hello World! Hello World! Hello World! Hello World! Hello World! Hello World!Hello World! Hello World! Hello World! Hello World! Hello World! Hello World!Hello World! Hello World! Hello World! Hello World! Hello World! Hello World!Hello World! Hello World! Hello World! Hello World! Hello World! Hello World!Hello World! Hello World! Hello World! Hello World! Hello World! Hello World!Hello World! Hello World! Hello World! Hello World! Hello World! Hello World!Hello World! Hello World! Hello World! Hello World! Hello World! Hello World!Hello World! Hello World! Hello World! Hello World! Hello World! Hello World!"
        label.textColor = .black

        view.addSubview(label)
        self.view = view
    }
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()

问题在于,如果我使用NSParagraphStyle,它将无法正确渲染或计算尺寸!

如果我删除段落样式,它渲染得很好,但从不服从换行模式,并且大小调整错误..

任何想法我做错了什么?如何让它遵循段落样式的换行模式?为什么在应用段落样式时总是将大小计算为21?

使用段落样式时: enter image description here

删除段落样式时: enter image description here

删除段落样式但在TruncateEnd上设置CATextLayer时(它从不截断,最后一行在它与倒数第二行之间的间距更大): enter image description here

1 个答案:

答案 0 :(得分:-1)

我发现省略号字形不是从属性字符串中获取其大小,而是从a属性中获取其大小。

默认情况下,这似乎是一个较大的值,这说明您的文本在第一个示例中被下移并剪切了。向下扩展文本层的框架大小,设置CATextLayer.fontSize的{​​{1}},您将看到您的文本以及一个巨大的...椭圆形字形!

设置CATextLayer以匹配属性文本的字体大小可以解决此问题,但它看起来像是Apple的bug。

我也无法通过单独设置属性字符串属性来使它起作用。