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
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()
}
}
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()
}
}
}
Swift 3 version for regular and attributed strings.
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()
}
}
gbk's code works. below is gbk's code updated for XCode 8 beta 6. Current as of 1 Oct 2016
Step 1. Subclass CATextLayer. In the code below I've named the subclass "MyCATextLayer" Outside your view controller class copy/paste the below code.
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()
}
}
Step 2. Within your view controller class in your ".swift" file, create your CATextLabel. In the code example I've named the subclass "MyDopeCATextLayer."
let MyDopeCATextLayer: MyCATextLayer = MyCATextLayer()
Step 3. Set your new CATextLayer with desired text/color/bounds/frame.
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)
Step 4. done
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.
The correct answer, as you've already found, is here in Objective-C and works for iOS. It works by subclassing the CATextLayer
and overriding the drawInContext
function.
However, I've made some improvements to the code, as shown below, using David Hoerl's code as a basis. The changes come solely in recalculating the vertical position of the text represented by the yDiff
. I've tested it with my own code.
Here is the code for Swift users:
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()
}
}