0

I am fresh to IOS development. I am looking for a way to retrieve detailed text layout information (such as ascent, descent, advance width etc).

In android, I am able to do this through Paint.getTextWidths. Then I am able to draw bounds or do hit test on individual character.

like this:

enter image description here

On IOS, I am using CATextLayer to manage text layers, but I could only find out the layer.frame, which gives me the bounds of the whole text block.

like this:

enter image description here

Any equivalence to easily do this on IOS?

Polar Bear
  • 71
  • 1
  • 6

1 Answers1

0

I finally achieved this by using CTLine and CTRun to manually measure the text.

let layer = CATextLayer()
        
layer.string = self.text
layer.fontSize = CGFloat(self.fontSize)
layer.font = CTFontCreateWithName("Helvetica" as CFString, CGFloat(self.fontSize), nil)
layer.truncationMode = .end
layer.allowsFontSubpixelQuantization = false
layer.contentsScale = UIScreen.main.scale
layer.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: layer.preferredFrameSize())
        
let attrs = [ NSAttributedString.Key.font: UIFont(name: "Helvetica", size: 20.0)! ]
let nsText = NSAttributedString(string: self.text, attributes: attrs)
        
let nsLine = CTLineCreateWithAttributedString(nsText)
let runs = CTLineGetGlyphRuns(nsLine) as? Array<CTRun>
        
// measure text
var widths = Array<CGFloat>()
for run in runs! {
    let glyphCount = CTRunGetGlyphCount(run)
    var cgSizes = Array(repeating: CGSize(), count: glyphCount)
    let _ = CTRunGetAdvances(run, CFRangeMake(0, glyphCount), &cgSizes)
        
    for cgSize in cgSizes {
        widths.append(cgSize.width)
    }
}
        
// draw bounding box
let height = layer.frame.height
print("advance widths: \(widths), height: \(height)")
var xOffset = CGFloat()
for width in widths {
            
    let path = UIBezierPath()
    path.move(to: CGPoint(x: xOffset, y: 0.0))
    path.addLine(to: CGPoint(x: xOffset + width, y: 0.0 ))
    path.addLine(to: CGPoint(x: xOffset + width, y: height))
    path.addLine(to: CGPoint(x: xOffset, y: height))
    path.close()
            
    let boundsLayer = CAShapeLayer()
    boundsLayer.path = path.cgPath
    boundsLayer.lineWidth = 1
    boundsLayer.strokeColor = UIColor.blue.cgColor
    boundsLayer.fillColor = CGColor(gray: 0, alpha: 0)
            
    layer.addSublayer(boundsLayer)
            
    xOffset += width
}
        
layer.borderWidth = 1
layer.borderColor = UIColor.red.cgColor
Polar Bear
  • 71
  • 1
  • 6