Last active
November 12, 2023 14:51
-
-
Save krzyzanowskim/e92eaf31e0419820c0f8cbcf96ba1269 to your computer and use it in GitHub Desktop.
Calculate frame of String, that fits given width
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Excerpt from https://github.com/krzyzanowskim/CoreTextWorkshop | |
// Licence BSD-2 clause | |
// Marcin Krzyzanowski [email protected] | |
func getSizeThatFits(_ attributedString: NSAttributedString, maxWidth: CGFloat) -> CGSize { | |
let framesetter = CTFramesetterCreateWithAttributedString(attributedString) | |
let rectPath = CGRect(origin: .zero, size: CGSize(width: maxWidth, height: 50000)) | |
let ctFrame = CTFramesetterCreateFrame(framesetter, CFRange(), CGPath(rect: rectPath, transform: nil), nil) | |
guard let ctLines = CTFrameGetLines(ctFrame) as? [CTLine], !ctLines.isEmpty else { | |
return .zero | |
} | |
var ctLinesOrigins = Array<CGPoint>(repeating: .zero, count: ctLines.count) | |
// Get origins in CoreGraphics coodrinates | |
CTFrameGetLineOrigins(ctFrame, CFRange(), &ctLinesOrigins) | |
// Transform last origin to iOS coordinates | |
let transform: CGAffineTransform | |
#if os(macOS) | |
transform = CGAffineTransform.identity | |
#else | |
transform = CGAffineTransform(scaleX: 1, y: -1).concatenating(CGAffineTransform.init(translationX: 0, y: rectPath.height)) | |
#endif | |
guard let lastCTLineOrigin = ctLinesOrigins.last?.applying(transform), let lastCTLine = ctLines.last else { | |
return .zero | |
} | |
// Get last line metrics and get full height (relative to from origin) | |
var ascent: CGFloat = 0 | |
var descent: CGFloat = 0 | |
var leading: CGFloat = 0 | |
CTLineGetTypographicBounds(lastCTLine, &ascent, &descent, &leading) | |
let lineSpacing = (floor(ascent + descent + leading) * 0.2) + 0.5 // 20% by default, actual value depends on Paragraph | |
let lineHeight = floor(ascent + descent + leading) + 0.5 | |
// Calculate maximum height of the frame | |
let maxHeight = lastCTLineOrigin.y + descent + leading + (lineSpacing / 2) | |
return CGSize(width: maxWidth, height: maxHeight) | |
} |
P.S. Personally I've been using NSAttributedString's boundingRect(with: options:) method for quite some time and it's been okay for the most part.
it's broken tho, it always has been. It works mostly, and when it fails - it depends on the font, characters, and given frames - depends on the context in general. I did use it by myself until it break the layout randomly, which pushed me to debug it further. You will find a bunch of questions about it eg. on StackOverflow.
Missed bracket on 36 line.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It should be
floor(value) + 0.5
for pixel aligning.