I'm creating a thing that puts text on top of images. The biggest allowed frame for the text to be drawn is specified by our backend, and I have to do my best to fit whatever text the user enters into that rectangle.
I looked up a ton of older answers, but all answers seem to either use UILabel
automatic fitting or just brute forcing String.boundingRect(with:options:attributes:context:)
.
My initial idea was to just divide use the height of the text rectangle as the point size, but this seems to not work 100% in addition to not supporting text's that will be too long horizontally.
Here's how I'm drawing stuff right now, just to get some context.
let image = renderer.image { context in
backgroundImage.draw(at: .zero)
let titleAttributes: [NSAttributedStringKey: Any] = [
.font: UIFont(name: fontName, size: maxSize.height)!,
.foregroundColor: UIColor.white,
.paragraphStyle: NSMutableParagraphStyle(alignment: alignment),
.shadow: NSShadow( color: .black, offset: .zero, radius: 28),
]
(title as NSString).draw(with: maxFrame,
options: .usesLineFragmentOrigin,
attributes: titleAttributes,
context: nil)
}
Is there any efficient way to figure out the point size of a font to fit inside a rectangle without using UILabels or brute forcing it?
I solved it myself again!
The text I'm drawing is never allowed to break line, so it's always one line. When making a brute force method in Playgrounds, I noticed the text width is linear to font size. So that made it really easy.
extension UIFont {
convenience init?(named fontName: String, fitting text: String, into targetSize: CGSize, with attributes: [NSAttributedStringKey: Any], options: NSStringDrawingOptions) {
var attributes = attributes
let fontSize = targetSize.height
attributes[.font] = UIFont(name: fontName, size: fontSize)
let size = text.boundingRect(with: CGSize(width: .greatestFiniteMagnitude, height: fontSize),
options: options,
attributes: attributes,
context: nil).size
let heightSize = targetSize.height / (size.height / fontSize)
let widthSize = targetSize.width / (size.width / fontSize)
self.init(name: fontName, size: min(heightSize, widthSize))
}
}
This extension will initiate a font that's guaranteed to fit inside the target rectangle as long as it's 1 line.