vertically align text in a CATextLayer?

2020-02-05 11:45发布

I am working on a CATextLayer that I want to use in both Mac and iOS. Can I control the vertical alignment of the text within the layer?

In this particular case, I want to center it vertically -- but information about other vertical alignments would also be of interest.

EDIT: I found this, but I can't make it work.

13条回答
欢心
2楼-- · 2020-02-05 12:14

I slightly modified this answer by @iamkothed. The differences are:

  • text height calculation is based on NSString.size(with: Attributes). I don't know if it's an improvement over (height-fontSize)/2 - fontSize/10, but I like to think that it is. Although, in my experience, NSString.size(with: Attributes) doesn't always return the most appropriate size.
  • added invertedYAxis property. It was useful for my purposes of exporting this CATextLayer subclass using AVVideoCompositionCoreAnimationTool. AVFoundation operates in "normal" y axis, and that's why I had to add this property.
  • Works only with NSString. You can use Swift's String class though, because it automatically casts to NSString.
  • It ignores CATextLayer.fontSize property and completely relies on CATextLayer.font property which MUST be a UIFont instance.

    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()
        }
    }
    
查看更多
forever°为你锁心
3楼-- · 2020-02-05 12:15

The code for Swift 3, based on code @iamktothed

If you use an attributed string for setting font properties, than you can use function size() from NSAttributedString to calculate height of string. I think this code also resolve the problems described by @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()
        }
    }
}
查看更多
家丑人穷心不美
4楼-- · 2020-02-05 12:17

So there is no "direct" way of doing this but you can accomplish the same thing by using text metrics:

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

... for example, find the size of the text then use that information to place it where you want in the parent layer. Hope this helps.

查看更多
姐就是有狂的资本
5楼-- · 2020-02-05 12:18

thank @iamktothed, it works. following is swift 3 version:

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()
}
}
查看更多
Bombasti
6楼-- · 2020-02-05 12:25

It is an late answer, but I have the same question these days, and have solved the problem with following investigation.

Vertical align depends on the text you need to draw, and the font you are using, so there is no one way solution to make it vertical for all cases.

But we can still calculate the vertical mid point for different cases.

According to apple's About Text Handling in iOS, we need to know how the text is drawn.

For example, I am trying to make vertical align for weekdays strings: Sun, Mon, Tue, ....

For this case, the height of the text depends on cap Height, and there is no descent for these characters. So if we need to make these text align to the middle, we can calculate the offset of the top of cap character, e.g. The position of the top of character "S".

According to the the figure below:

fig

The top space for the capital character "S" would be

font.ascender - font.capHeight

And the bottom space for the capital character "S" would be

font.descender + font.leading

So we need to move "S" a little bit off the top by:

y = (font.ascender - font.capHeight + font.descender + font.leading + font.capHeight) / 2

That equals to:

y = (font.ascender + font.descender + font.leading) / 2

Then I can make the text vertical align middle.

Conclusion:

  1. If your text does not include any character exceed the baseline, e.g. "p", "j", "g", and no character over the top of cap height, e.g. "f". The you can use the formula above to make the text align vertical.

    y = (font.ascender + font.descender + font.leading) / 2
    
  2. If your text include character below the baseline, e.g. "p", "j", and no character exceed the top of cap height, e.g. "f". Then the vertical formula would be:

    y = (font.ascender + font.descender) / 2
    
  3. If your text include does not include character drawn below the baseline, e.g. "j", "p", and does include character drawn above the cap height line, e.g. "f". Then y would be:

    y = (font.descender + font.leading) / 2
    
  4. If all characters would be occurred in your text, then y equals to:

    y = font.leading / 2
    
查看更多
SAY GOODBYE
7楼-- · 2020-02-05 12:27

You need to know where CATextLayer will put the baseline of your text. Once you know that, offset the coordinate system within the layer, i.e. adjust bounds.origin.y by the difference between where the baseline normally sits and where you want it to be, given the metrics of the font.

CATextLayer is a bit of a black box and finding where the baseline will sit is a bit tricky - see my answer here for iOS - I've no idea what the behaviour is on Mac.

查看更多
登录 后发表回答