问题描述
我正在尝试创建一个具有动画属性的自定义标签,因此我决定使用 CATextLayer
而不是直接使用 CoreText
..
I am trying to create a custom label that has animatable properties so I decided to use CATextLayer
instead of going straight to CoreText
..
我想出了以下代码(我使用 Playground 来测试):
I came up with the following code (I used playground to test things):
//: 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
,它根本不会正确渲染或计算大小!
The problem is that if I use NSParagraphStyle
, it will NOT render or calculate the size properly at all!
如果我删除段落样式,它呈现良好但从不遵守换行模式并且大小错误..
If I remove the paragraph style, it renders fine but never obeys the line-break mode and the sizing is wrong..
知道我做错了什么吗?我怎样才能让它遵守段落样式的换行模式?为什么应用段落样式时总是将大小计算为 21?
Any ideas what I'm doing wrong? How can I get it to obey the line-break mode with paragraph style? Why does it ALWAYS calculate the size as 21 when paragraph style is applied?
使用段落样式时:
删除段落样式时:
删除段落样式但在 CATextLayer
上设置 TruncateEnd
时(它永远不会截断,最后一行与倒数第二行之间的间距更大):
When removing Paragraph Style but settings TruncateEnd
on the CATextLayer
(it never truncates and the last line has way more spacing between it and the second-last line):
推荐答案
我发现结束省略号字形不是从属性字符串中获取其大小,而是从 CATextLayer.fontSize
属性中获取.
I've discovered that the end-ellipsis glyph is not taking it's size from the attributed string but rather the CATextLayer.fontSize
property.
这似乎默认为一个大值,这解释了您的文本在第一个示例中被向下移动和剪切的原因.向下扩展文本层的边框大小,设置CATextLayer
的isWrapped=false
,你会看到你的文本加上一个巨大的......省略号字形!
This seems to default to a large value which explains your text being shifted down and clipped in your first example. Expand the text layer's frame size downward, set CATextLayer
's isWrapped=false
, and you will see your text plus a huge ... ellipis glyph!
设置 CATextLayer.fontSize
以匹配属性文本的字体大小可以解决这个问题,但它看起来像一个 Apple 错误.
Setting the CATextLayer.fontSize
to match the font size of your attributed text works around this issue, but it looks like an Apple bug.
我也无法通过单独设置属性字符串属性来使其工作..
I can't get it work by setting attributed string properties alone either..
这篇关于CATextLayer 渲染带有截断和段落样式的 AttributedString的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!