Resizing text to fit available space

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)
    }
}

Could the problem be in the approximation when using fractional font size ?

Did you try using

func size(withAttributes attrs: [NSAttributedString.Key : Any]? = nil) -> CGSize

and adapt the size to fit window width ?

Some more details here: https://stackoverflow.com/questions/1324379/how-to-calculate-the-width-of-a-text-string-of-a-specific-font-and-font-size

That's exactly the question: how do I adapt the size to fit the window width? I don't see how size(withAttributes:) would help me finding out the correct font size with a single call.

I don't know if that answers, but I would:

  • considering present font size is actualSize
  • and windowWidth are pastWidth and newWidth
  • let newSize = text.size(withAttributes: actualSize) * newWidth / pastWidth
  • change font size to newSize

size(withAttributes:) returns CGSize not a number. Why wouldn't you calculate newSize = actualSize * newWidth / pastWidth instead? But that's the same as calculating newSize = newWidth * constant, which as I showed doesn't work.

You're right.

I need to go and test in code.

I'll check what is the new text width with the new size and see if you need an extra adjustment (because string with may not be exactly proportional to font width).

Did you try with monospaced font, just to see.

If it works, that confirms that the cause is that string width is not exactly proportional to font size.

With the system monospaced font it seems to work, regardless whether I use the window width or height to calculate the font size. On the other hand, with the standard system font, it doesn't work with either width or height. I thought the font size is related to the height, in which case using a monospaced font (where characters have equal width) should make no difference.

Resizing text to fit available space
 
 
Q