Working on this problem and following a lot of different answers from several posters, I had implemented a solution for the all mighty problem of correct text size, for me CTFramesetterSuggestFrameSizeWithConstraints
is not working properly so we need to use CTFramesetterCreateFrame
and then measure the size for that frame, (this is a UIFont
extension) this is swift 100%
References
CTFramesetterSuggestFrameSizeWithConstraints sometimes returns incorrect size?
How to get the real height of text drawn on a CTFrame
Using CFArrayGetValueAtIndex in Swift with UnsafePointer (AUPreset)
func sizeOfString (string: String, constrainedToWidth width: Double) -> CGSize {
let attributes = [NSAttributedString.Key.font:self]
let attString = NSAttributedString(string: string,attributes: attributes)
let framesetter = CTFramesetterCreateWithAttributedString(attString)
let frame = CTFramesetterCreateFrame(framesetter,CFRange(location: 0,length: 0),CGPath.init(rect: CGRect(x: 0, y: 0, width: width, height: 10000), transform: nil),nil)
return UIFont.measure(frame: frame)
}
Then we measure our CTFrame
static func measure(frame:CTFrame) ->CGSize {
let lines = CTFrameGetLines(frame)
let numOflines = CFArrayGetCount(lines)
var maxWidth : Double = 0
for index in 0..<numOflines {
let line : CTLine = unsafeBitCast(CFArrayGetValueAtIndex(lines, index), to: CTLine.self)
var ascent : CGFloat = 0
var descent : CGFloat = 0
var leading : CGFloat = 0
let width = CTLineGetTypographicBounds(line, &ascent, &descent, &leading)
if(width > maxWidth) {
maxWidth = width
}
}
var ascent : CGFloat = 0
var descent : CGFloat = 0
var leading : CGFloat = 0
CTLineGetTypographicBounds(unsafeBitCast(CFArrayGetValueAtIndex(lines, 0), to: CTLine.self), &ascent, &descent, &leading)
let firstLineHeight = ascent + descent + leading
CTLineGetTypographicBounds(unsafeBitCast(CFArrayGetValueAtIndex(lines, numOflines - 1), to: CTLine.self), &ascent, &descent, &leading)
let lastLineHeight = ascent + descent + leading
var firstLineOrigin : CGPoint = CGPoint(x: 0, y: 0)
CTFrameGetLineOrigins(frame, CFRangeMake(0, 1), &firstLineOrigin);
var lastLineOrigin : CGPoint = CGPoint(x: 0, y: 0)
CTFrameGetLineOrigins(frame, CFRangeMake(numOflines - 1, 1), &lastLineOrigin);
let textHeight = abs(firstLineOrigin.y - lastLineOrigin.y) + firstLineHeight + lastLineHeight
return CGSize(width: maxWidth, height: Double(textHeight))
}