28

我正在开发一个我想在 Mac 和 iOS 中使用的 CATextLayer。我可以控制图层内文本的垂直对齐方式吗?

在这种特殊情况下,我想将其垂直居中 - 但有关其他垂直对齐方式的信息也很有趣。

编辑:我找到了这个,但我不能让它工作。

4

16 回答 16

33

正如您已经找到的,正确的答案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()
    }
}
于 2015-09-08T16:05:54.283 回答
15

这是一个迟到的答案,但这些天我有同样的问题,并通过以下调查解决了这个问题。

垂直对齐取决于您需要绘制的文本以及您使用的字体,因此没有一种方法可以在所有情况下使其垂直。

但是我们仍然可以计算不同情况下的垂直中点。

根据苹果的关于 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

然后我可以使文本垂直居中对齐。

结论:

  1. 如果您的文本不包含任何超出基线的字符,例如“p”、“j”、“g”,并且没有超过大写高度顶部的字符,例如“f”。您可以使用上面的公式使文本垂直对齐。

    y = (font.ascender + font.descender + font.leading) / 2
    
  2. 如果您的文本包含低于基线的字符,例如“p”、“j”,并且没有字符超过大写高度的顶部,例如“f”。那么垂直公式将是:

    y = (font.ascender + font.descender) / 2
    
  3. 如果您的文本包含不包括在基线下方绘制的字符,例如“j”、“p”,并且确实包括在大写高度线上方绘制的字符,例如“f”。那么 y 将是:

    y = (font.descender + font.leading) / 2
    
  4. 如果所有字符都出现在您的文本中,则 y 等于:

    y = font.leading / 2
    
于 2017-02-18T02:45:20.690 回答
13

也许回答迟了,但你可以计算文本的大小,然后设置 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];
于 2015-02-17T09:44:42.447 回答
9

用于常规字符串和属性字符串的 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()
    }
}
于 2017-05-18T07:12:36.107 回答
6

谢谢@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()
}
}
于 2017-01-07T06:20:47.640 回答
3

没有什么能阻止您使用具有 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 的代码处理的,而不是在布局代码中。

于 2019-10-01T22:02:49.297 回答
2

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. 完成

于 2016-10-01T16:02:31.743 回答
2

更新这个线程(对于单行和多行 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()
    }
}
于 2020-06-24T17:22:22.507 回答
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()
        }
    }
}
于 2017-03-12T10:24:01.477 回答
1

我通过@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()
        }
    }
    
于 2019-02-16T15:04:10.323 回答
0

所以没有“直接”的方式来做到这一点,但你可以通过使用文本度量来完成同样的事情:

http://developer.apple.com/library/ios/#documentation/UIKit/Reference/NSString_UIKit_Additions/Reference/Reference.html

...例如,找到文本的大小,然后使用该信息将其放置在父层中您想要的位置。希望这可以帮助。

于 2011-06-25T03:30:49.870 回答
0

您需要知道 CATextLayer 将放置文本基线的位置。一旦你知道了,偏移层内的坐标系,即调整 bounds.origin.y 通过基线通常位置和你想要的位置之间的差异,给定字体的度量。

CATextLayer 有点像一个黑盒子,找到基线的位置有点棘手 - 请参阅我的 iOS 答案- 我不知道 Mac 上的行为是什么。

于 2012-10-20T10:18:25.123 回答
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: 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()
    }
}
于 2019-03-25T20:53:16.340 回答
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!

于 2020-08-21T19:23:46.697 回答
0
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()
}

}

归功于:

https://github.com/cemolcay/CenterTextLayer

于 2021-10-01T22:27:57.447 回答
-3

据我所知,我的问题的答案是“不”。

于 2011-01-23T17:36:44.567 回答