我正在开发一个我想在Mac和iOS上使用的CATextLayer。我可以控制图层中文本的垂直对齐吗?
在这种特殊情况下,我希望将其垂直居中 - 但有关其他垂直对齐的信息也会引起关注。
编辑:我找到了this,但我无法使其发挥作用。答案 0 :(得分:24)
正如您已经找到的,正确的答案是Objective-C中的here和适用于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()
}
}
答案 1 :(得分:11)
也许迟到的答案,但你可以计算文字的大小,然后设置textLayer的位置。您还需要将textLayer textAligment模式设置为" center"
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];
答案 2 :(得分:6)
这是一个迟到的答案,但这些天我有同样的问题,并通过以下调查解决了这个问题。
垂直对齐取决于您需要绘制的文字以及您使用的字体,因此没有一种解决办法可以使其适用于所有情况。
但我们仍然可以计算不同情况下的垂直中点。
根据apple的About Text Handling in iOS,我们需要知道如何绘制文本。
例如,我正在尝试为工作日字符串进行垂直对齐:Sun,Mon,Tue,....
对于这种情况,文字的高度取决于上限高度,并且这些字符没有下降。因此,如果我们需要使这些文本与中间对齐,我们可以计算顶部字符的偏移量,例如字符“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 = (font.descender + font.leading) / 2
如果您的文字中出现所有字符,则y等于:
y = font.leading / 2
答案 3 :(得分:5)
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()
}
}
答案 4 :(得分:4)
感谢@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()
}
}
答案 5 :(得分:1)
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。在代码示例中,我将子类命名为#34; 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.完成
答案 6 :(得分:1)
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()
}
}
}
答案 7 :(得分:1)
更新此线程(对于单行和多行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()
}
}
答案 8 :(得分:0)
所以没有“直接”的方式来做到这一点,但你可以通过使用文本指标来完成同样的事情:
...例如,找到文本的大小,然后使用该信息将其放置在父图层中所需的位置。希望这会有所帮助。
答案 9 :(得分:0)
您需要知道CATextLayer放置文本基线的位置。一旦你知道这一点,就会偏移图层中的坐标系,即根据字体的指标,通过基线通常所在的位置和你想要的位置之间的差异调整bounds.origin.y。
CATextLayer是一个黑盒子,找到基线所在的位置有点棘手 - 请参阅我的answer here for iOS - 我不知道Mac上的行为是什么。
答案 10 :(得分:0)
我对this answer by @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()
}
}
答案 11 :(得分:0)
我想提出一种解决方案,将可用包装盒内的多行换行考虑在内:
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: NSStringDrawingOptions.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()
}
}
答案 12 :(得分:0)
没有什么可以阻止您使用具有CATextLayer作为子层的通用CALayer(容器)创建CALayer层次结构。
无需计算CATextLayer的字体大小,只需计算CALayer中CATextLayer的偏移量,以使其垂直居中。如果将文本层的对齐方式设置为居中,并使文本层的宽度与封闭容器相同,则它也将水平居中。
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的代码处理的,而不是由布局代码处理的。
答案 13 :(得分:0)
适用于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!
答案 14 :(得分:-3)
我可以说,我的问题的答案是“不。”