垂直对齐CATextLayer中的文本?

时间:2011-01-22 01:03:00

标签: iphone macos ios core-animation text-alignment

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

在这种特殊情况下,我希望将其垂直居中 - 但有关其他垂直对齐的信息也会引起关注。

编辑:我找到了this,但我无法使其发挥作用。

15 个答案:

答案 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”顶部的位置。

根据下图:

fig

首字符“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 = (font.descender + font.leading) / 2
    
  4. 如果您的文字中出现所有字符,则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)

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

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

...例如,找到文本的大小,然后使用该信息将其放置在父图层中所需的位置。希望这会有所帮助。

答案 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)

我可以说,我的问题的答案是“不。”