我正在开发一个我想在 Mac 和 iOS 中使用的 CATextLayer。我可以控制图层内文本的垂直对齐方式吗?
在这种特殊情况下,我想将其垂直居中 - 但有关其他垂直对齐方式的信息也很有趣。
编辑:我找到了这个,但我不能让它工作。
我正在开发一个我想在 Mac 和 iOS 中使用的 CATextLayer。我可以控制图层内文本的垂直对齐方式吗?
在这种特殊情况下,我想将其垂直居中 - 但有关其他垂直对齐方式的信息也很有趣。
编辑:我找到了这个,但我不能让它工作。
正如您已经找到的,正确的答案在Objective-C 中,适用于 iOS。它通过子类化CATextLayer
和覆盖drawInContext
函数来工作。
不过,我对代码做了一些改进,如下所示,使用 David Hoerl 的代码作为基础。更改仅来自重新计算.表示的文本的垂直位置yDiff
。我已经用我自己的代码对其进行了测试。
以下是 Swift 用户的代码:
class LCTextLayer : CATextLayer {
// REF: http://lists.apple.com/archives/quartz-dev/2008/Aug/msg00016.html
// CREDIT: David Hoerl - https://github.com/dhoerl
// USAGE: To fix the vertical alignment issue that currently exists within the CATextLayer class. Change made to the yDiff calculation.
override func draw(in context: CGContext) {
let height = self.bounds.size.height
let fontSize = self.fontSize
let yDiff = (height-fontSize)/2 - fontSize/10
context.saveGState()
context.translateBy(x: 0, y: yDiff) // Use -yDiff when in non-flipped coordinates (like macOS's default)
super.draw(in: context)
context.restoreGState()
}
}
这是一个迟到的答案,但这些天我有同样的问题,并通过以下调查解决了这个问题。
垂直对齐取决于您需要绘制的文本以及您使用的字体,因此没有一种方法可以在所有情况下使其垂直。
但是我们仍然可以计算不同情况下的垂直中点。
根据苹果的关于 iOS 中的文本处理,我们需要知道文本是如何绘制的。
例如,我正在尝试为工作日字符串进行垂直对齐:Sun, Mon, Tue, ....
对于这种情况,文本的高度取决于cap Height,并且这些字符没有下降。所以如果我们需要让这些文本居中对齐,我们可以计算大写字符顶部的偏移量,例如字符“S”的顶部位置。
根据下图:
大写字母“S”的顶部空间将是
font.ascender - font.capHeight
大写字母“S”的底部空间将是
font.descender + font.leading
所以我们需要通过以下方式将“S”从顶部移动一点:
y = (font.ascender - font.capHeight + font.descender + font.leading + font.capHeight) / 2
这等于:
y = (font.ascender + font.descender + font.leading) / 2
然后我可以使文本垂直居中对齐。
如果您的文本不包含任何超出基线的字符,例如“p”、“j”、“g”,并且没有超过大写高度顶部的字符,例如“f”。您可以使用上面的公式使文本垂直对齐。
y = (font.ascender + font.descender + font.leading) / 2
如果您的文本包含低于基线的字符,例如“p”、“j”,并且没有字符超过大写高度的顶部,例如“f”。那么垂直公式将是:
y = (font.ascender + font.descender) / 2
如果您的文本包含不包括在基线下方绘制的字符,例如“j”、“p”,并且确实包括在大写高度线上方绘制的字符,例如“f”。那么 y 将是:
y = (font.descender + font.leading) / 2
如果所有字符都出现在您的文本中,则 y 等于:
y = font.leading / 2
也许回答迟了,但你可以计算文本的大小,然后设置 textLayer 的位置。您还需要将 textLayer textAligment 模式设置为“中心”
CGRect labelRect = [text boundingRectWithSize:view.bounds.size options:NSStringDrawingUsesLineFragmentOrigin attributes:@{ NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue" size:17.0] } context:nil];
CATextLayer *textLayer = [CATextLayer layer];
[textLayer setString:text];
[textLayer setForegroundColor:[UIColor redColor].CGColor];
[textLayer setFrame:labelRect];
[textLayer setFont:CFBridgingRetain([UIFont fontWithName:@"HelveticaNeue" size:17.0].fontName)];
[textLayer setAlignmentMode:kCAAlignmentCenter];
[textLayer setFontSize:17.0];
textLayer.masksToBounds = YES;
textLayer.position = CGPointMake(CGRectGetMidX(view.bounds), CGRectGetMidY(view.bounds));
[view.layer addSublayer:textLayer];
用于常规字符串和属性字符串的 Swift 3 版本。
class ECATextLayer: CATextLayer {
override open func draw(in ctx: CGContext) {
let yDiff: CGFloat
let fontSize: CGFloat
let height = self.bounds.height
if let attributedString = self.string as? NSAttributedString {
fontSize = attributedString.size().height
yDiff = (height-fontSize)/2
} else {
fontSize = self.fontSize
yDiff = (height-fontSize)/2 - fontSize/10
}
ctx.saveGState()
ctx.translateBy(x: 0.0, y: yDiff)
super.draw(in: ctx)
ctx.restoreGState()
}
}
谢谢@iamktothed,它有效。以下是 swift 3 版本:
class CXETextLayer : CATextLayer {
override init() {
super.init()
}
override init(layer: Any) {
super.init(layer: layer)
}
required init(coder aDecoder: NSCoder) {
super.init(layer: aDecoder)
}
override func draw(in ctx: CGContext) {
let height = self.bounds.size.height
let fontSize = self.fontSize
let yDiff = (height-fontSize)/2 - fontSize/10
ctx.saveGState()
ctx.translateBy(x: 0.0, y: yDiff)
super.draw(in: ctx)
ctx.restoreGState()
}
}
没有什么能阻止您使用具有 CATextLayer 作为子层的通用 CALayer(容器)创建 CALayer 层次结构。
无需计算 CATextLayer 的字体大小,只需计算 CATextLayer 在 CALayer 内的偏移量,使其垂直居中即可。如果将文本图层的对齐方式设置为居中,并使文本图层的宽度与封闭容器相同,则它也会水平居中。
let container = CALayer()
let textLayer = CATextLayer()
// create the layer hierarchy
view.layer.addSublayer(container)
container.addSublayer(textLayer)
// Setup the frame for your container
...
// Calculate the offset of the text layer so that it is centred
let hOffset = (container.frame.size.height - textLayer.frame.size.height) * 0.5
textLayer.frame = CGRect(x:0.0, y: hOffset, width: ..., height: ...)
子层框架相对于其父层,因此计算相当简单。此时无需关心字体大小。这是由处理 CATextLayer 的代码处理的,而不是在布局代码中。
gbk 的代码有效。以下是针对 XCode 8 beta 6 更新的 gbk 代码。截至 2016 年 10 月 1 日
步骤 1. 子类 CATextLayer。在下面的代码中,我将子类命名为“MyCATextLayer” 在您的视图控制器类之外复制/粘贴下面的代码。
class MyCATextLayer: CATextLayer {
// REF: http://lists.apple.com/archives/quartz-dev/2008/Aug/msg00016.html
// CREDIT: David Hoerl - https://github.com/dhoerl
// USAGE: To fix the vertical alignment issue that currently exists within the CATextLayer class. Change made to the yDiff calculation.
override init() {
super.init()
}
required init(coder aDecoder: NSCoder) {
super.init(layer: aDecoder)
}
override func draw(in ctx: CGContext) {
let height = self.bounds.size.height
let fontSize = self.fontSize
let yDiff = (height-fontSize)/2 - fontSize/10
ctx.saveGState()
ctx.translateBy(x: 0.0, y: yDiff)
super.draw(in: ctx)
ctx.restoreGState()
}
}
第 2 步。在“.swift”文件的视图控制器类中,创建 CATextLabel。在代码示例中,我将子类命名为“MyDopeCATextLayer”。
let MyDopeCATextLayer: MyCATextLayer = MyCATextLayer()
步骤 3. 使用所需的文本/颜色/边界/框架设置新的 CATextLayer。
MyDopeCATextLayer.string = "Hello World" // displayed text
MyDopeCATextLayer.foregroundColor = UIColor.purple.cgColor //color of text is purple
MyDopeCATextLayer.frame = CGRect(x: 0, y:0, width: self.frame.width, height: self.frame.height)
MyDopeCATextLayer.font = UIFont(name: "HelveticaNeue-UltraLight", size: 5) //5 is ignored, set actual font size using ".fontSize" (below)
MyDopeCATextLayer.fontSize = 24
MyDopeCATextLayer.alignmentMode = kCAAlignmentCenter //Horizontally centers text. text is automatically centered vertically because it's set in subclass code
MyDopeCATextLayer.contentsScale = UIScreen.main.scale //sets "resolution" to whatever the device is using (prevents fuzzyness/blurryness)
步骤 4. 完成
更新这个线程(对于单行和多行 CATextLayer),结合上面的一些答案。
class VerticalAlignedTextLayer : CATextLayer {
func calculateMaxLines() -> Int {
let maxSize = CGSize(width: frame.size.width, height: CGFloat(Float.infinity))
let font = UIFont(descriptor: self.font!.fontDescriptor, size: self.fontSize)
let charSize = font.lineHeight
let text = (self.string ?? "") as! NSString
let textSize = text.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
let linesRoundedUp = Int(ceil(textSize.height/charSize))
return linesRoundedUp
}
override func draw(in context: CGContext) {
let height = self.bounds.size.height
let fontSize = self.fontSize
let lines = CGFloat(calculateMaxLines())
let yDiff = (height - lines * fontSize) / 2 - lines * fontSize / 10
context.saveGState()
context.translateBy(x: 0, y: yDiff) // Use -yDiff when in non-flipped coordinates (like macOS's default)
super.draw(in: context)
context.restoreGState()
}
}
Swift 3 的代码,基于代码 @iamktothed
如果您使用属性字符串来设置字体属性,则可以使用 NSAttributedString 中的函数 size() 来计算字符串的高度。我认为这段代码也解决了@Enix 描述的问题
class LCTextLayer: CATextLayer {
override init() {
super.init()
}
override init(layer: Any) {
super.init(layer: layer)
}
required init(coder aDecoder: NSCoder) {
super.init(layer: aDecoder)
}
override open func draw(in ctx: CGContext) {
if let attributedString = self.string as? NSAttributedString {
let height = self.bounds.size.height
let stringSize = attributedString.size()
let yDiff = (height - stringSize.height) / 2
ctx.saveGState()
ctx.translateBy(x: 0.0, y: yDiff)
super.draw(in: ctx)
ctx.restoreGState()
}
}
}
我通过@iamkothed稍微修改了这个答案。区别在于:
NSString.size(with: Attributes)
. 我不知道这是否是一种改进(height-fontSize)/2 - fontSize/10
,但我喜欢这样认为。虽然,根据我的经验,NSString.size(with: Attributes)
并不总是返回最合适的尺寸。invertedYAxis
的属性。它对我CATextLayer
使用AVVideoCompositionCoreAnimationTool
. AVFoundation 在“正常”y 轴上运行,这就是我必须添加此属性的原因。NSString
. 不过你可以使用 Swift 的String
类,因为它会自动转换为NSString
.它忽略CATextLayer.fontSize
属性并完全依赖 CATextLayer.font
必须是UIFont
实例的属性。
class VerticallyCenteredTextLayer: CATextLayer {
var invertedYAxis: Bool = true
override func draw(in ctx: CGContext) {
guard let text = string as? NSString, let font = self.font as? UIFont else {
super.draw(in: ctx)
return
}
let attributes = [NSAttributedString.Key.font: font]
let textSize = text.size(withAttributes: attributes)
var yDiff = (bounds.height - textSize.height) / 2
if !invertedYAxis {
yDiff = -yDiff
}
ctx.saveGState()
ctx.translateBy(x: 0.0, y: yDiff)
super.draw(in: ctx)
ctx.restoreGState()
}
}
所以没有“直接”的方式来做到这一点,但你可以通过使用文本度量来完成同样的事情:
...例如,找到文本的大小,然后使用该信息将其放置在父层中您想要的位置。希望这可以帮助。
您需要知道 CATextLayer 将放置文本基线的位置。一旦你知道了,偏移层内的坐标系,即调整 bounds.origin.y 通过基线通常位置和你想要的位置之间的差异,给定字体的度量。
CATextLayer 有点像一个黑盒子,找到基线的位置有点棘手 - 请参阅我的 iOS 答案- 我不知道 Mac 上的行为是什么。
我想提出一个解决方案,将可用框内的多行换行考虑在内:
final class CACenteredTextLayer: CATextLayer {
override func draw(in ctx: CGContext) {
guard let attributedString = string as? NSAttributedString else { return }
let height = self.bounds.size.height
let boundingRect: CGRect = attributedString.boundingRect(
with: CGSize(width: bounds.width,
height: CGFloat.greatestFiniteMagnitude),
options: NSString.DrawingOptions.usesLineFragmentOrigin,
context: nil)
let yDiff: CGFloat = (height - boundingRect.size.height) / 2
ctx.saveGState()
ctx.translateBy(x: 0.0, y: yDiff)
super.draw(in: ctx)
ctx.restoreGState()
}
}
适用于 macOS 的 Swift 5.3
class VerticallyAlignedTextLayer : CATextLayer {
/* Credit - purebreadd - 6/24/2020
https://stackoverflow.com/questions/4765461/vertically-align-text-in-a-catextlayer
*/
func calculateMaxLines() -> Int {
let maxSize = CGSize(width: frame.size.width, height: frame.size.width)
let font = NSFont(descriptor: self.font!.fontDescriptor, size: self.fontSize)
let charSize = floor(font!.capHeight)
let text = (self.string ?? "") as! NSString
let textSize = text.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font!], context: nil)
let linesRoundedUp = Int(floor(textSize.height/charSize))
return linesRoundedUp
}
override func draw(in context: CGContext) {
let height = self.bounds.size.height
let fontSize = self.fontSize
let lines = CGFloat(calculateMaxLines())
let yDiff = -(height - lines * fontSize) / 2 - lines * fontSize / 6.5 // Use -(height - lines * fontSize) / 2 - lines * fontSize / 6.5 when in non-flipped coordinates (like macOS's default)
context.saveGState()
context.translateBy(x: 0, y: yDiff)
super.draw(in: context)
context.restoreGState()
}
}
请注意,我将 fontSize 除以 6.5,这似乎更适合我应用此解决方案。谢谢@purebreadd!
class CenterTextLayer: CATextLayer {
override func draw(in ctx: CGContext) {
#if os(iOS) || os(tvOS)
let multiplier = CGFloat(1)
#elseif os(OSX)
let multiplier = CGFloat(-1)
#endif
let yDiff = (bounds.size.height - ((string as? NSAttributedString)?.size().height ?? fontSize)) / 2 * multiplier
ctx.saveGState()
ctx.translateBy(x: 0.0, y: yDiff)
super.draw(in: ctx)
ctx.restoreGState()
}
}
归功于:
据我所知,我的问题的答案是“不”。