My app displays some text that should appear the same regardless of the container view or window size, i.e. it should grow and shrink with the container view or window.
On iOS there is UILabel.adjustsFontSizeToFitWidth
but I couldn't find any equivalent API on macOS. On the internet some people suggest to iteratively set a smaller font size until the text fits the available space, but I thought there must be a more efficient solution. How does UILabel.adjustsFontSizeToFitWidth
do it?
My expectation was that setting a font's size to a fraction of the window width or height would do the trick, but when resizing the window I can see a slightly different portion of it.
class ViewController: NSViewController {
override func loadView() {
view = MyView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
NSLayoutConstraint.activate([view.widthAnchor.constraint(equalTo: view.heightAnchor, multiplier: 3), view.heightAnchor.constraint(greaterThanOrEqualToConstant: 100)])
}
}
class MyView: NSView {
let textField = NSTextField(labelWithString: String(repeating: "a b c d e f g h i j k l m n o p q r s t u v w x y z ", count: 2))
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
textField.translatesAutoresizingMaskIntoConstraints = false
textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
addSubview(textField)
NSLayoutConstraint.activate([textField.topAnchor.constraint(equalTo: topAnchor), textField.leadingAnchor.constraint(equalTo: leadingAnchor), textField.trailingAnchor.constraint(equalTo: trailingAnchor)])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func resize(withOldSuperviewSize oldSize: NSSize) {
// textField.font = .systemFont(ofSize: frame.width * 0.05)
textField.font = .systemFont(ofSize: frame.height * 0.1)
}
}
By "some text that should appear the same," did you mean that the text size (both width and height) changes proportionally with the window size? If yes, changing the font size doesn't quite help.
The reason is that the text size doesn't change proportionally with the font size, and this is as-designed to achieve better text clarity. Concretely, if you double your window size (both width and height), your code will double the font size, and yet, the width of the spaces in your text (and also other characters) won't be doubled. As a result, your window will show more characters. For multi-line text, this in-proportional change can lead to different line break points, the text can hence look very different.
To achieve a feature similar to UILabel.adjustsFontSizeToFitWidth
on macOS, I don't see a great solution, and hence would suggest that you file a feedback report to request an API for that purpose – If you do so, please share your report ID here for folks to track.
You might still "iteratively set a smaller font size until the text fits the available space," and try to improve the efficiency in some way. For brain storming, here is an example:
-
Pick a character that has an average width. Assuming it is a piece of English text, "i" is probably the thinnest character and "W" or "M" are probably the widest ones.
-
Use the character + size(withAttributes:) to calculate the average character width of the current font.
-
Estimate the length of your text by timing the width with the character count of the text.
-
Compare the estimation against your taget width (the window width), and use the difference to estimate a good font size.
-
Use the font with the estimated font size + size(withAttributes:) to calculate the real width of your text.
-
Repeat from step 2, until the real width is good enough.
I haven't really implemented this flow, but step 2 ~ 4 may reduce the calls of size(withAttributes:)
, which may help, especially when the text is relatively long.
Worth mentioning though, adjustsFontSizeToFitWidth
doesn't provide a pixel perfection result. If your use case requires pixel perfection, consider converting your text to an image and scale the image.
Best,
——
Ziqiao Chen
Worldwide Developer Relations.