How can I have the text scale to fit the bounds I gave it?
Get NSTextField contents to scale
Asked Answered
I've done something like this in the past.
-(void)calcFontSizeToFitRect:(NSRect)r {
float targetWidth = r.size.width - xMargin;
float targetHeight = r.size.height - yMargin;
// the strategy is to start with a small font size and go larger until I'm larger than one of the target sizes
int i;
for (i=minFontSize; i<maxFontSize; i++) {
NSDictionary* attrs = [[NSDictionary alloc] initWithObjectsAndKeys:[NSFont fontWithName:currentFontName size:i], NSFontAttributeName, nil];
NSSize strSize = [stringValue sizeWithAttributes:attrs];
[attrs release];
if (strSize.width > targetWidth || strSize.height > targetHeight) break;
}
[self setCurrentFontSize:(i-1)];
}
The stringValue
variable is the text you want sized. The xMargin
and yMargin
variables are for spacing that you want. The minFontSize
and maxFontSize
variables give limits to how small or large you want to go.
Any solution for multiline text fields ? I would appreciate ! –
Whereas
When
maxFontSize
is large, and the font doesn't need to be exactly the maximum point size that will fit the container, you can get better performance (e.g. when the window is being resized by the user) by making the loop variable a float instead of an int, and incrementing it by multiplying itself by a factor like 1.1
instead of increasing it by a flat 1
. –
Meridethmeridian Also, it's kind of weird that here in late 2021, we still have to write custom code for this because
NSTextField
doesn't provide this capability out-of-the-box, like UILabel
does with its adjustsFontSizeToFitWidth
property (https://mcmap.net/q/125803/-dynamically-changing-font-size-of-uilabel). –
Meridethmeridian This solution appropriated from iOS works quite well. However, one thing to note: If you are setting this up programatically, you need to initialise your NSTextfield with a rect that has a width and height, otherwise the bounds
returns 0.
Also here's the link where I found this solution: https://medium.com/@joncardasis/dynamic-text-resizing-in-swift-3da55887beb3
extension NSFont {
/**
Will return the best font conforming to the descriptor which will fit in the provided bounds.
*/
static func bestFittingFontSize(for text: String, in bounds: CGRect, fontDescriptor: NSFontDescriptor, additionalAttributes: [NSAttributedString.Key: Any]? = nil) -> CGFloat {
let constrainingDimension = min(bounds.width, bounds.height)
let properBounds = CGRect(origin: .zero, size: bounds.size)
var attributes = additionalAttributes ?? [:]
let infiniteBounds = CGSize(width: CGFloat.infinity, height: CGFloat.infinity)
var bestFontSize: CGFloat = constrainingDimension
for fontSize in stride(from: bestFontSize, through: 0, by: -1) {
let newFont = NSFont(descriptor: fontDescriptor, size: fontSize)
attributes[.font] = newFont
let currentFrame = text.boundingRect(with: infiniteBounds, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: attributes, context: nil)
if properBounds.contains(currentFrame) {
bestFontSize = fontSize
break
}
}
return bestFontSize
}
static func bestFittingFont(for text: String, in bounds: CGRect, fontDescriptor: NSFontDescriptor, additionalAttributes: [NSAttributedString.Key: Any]? = nil) -> NSFont {
let bestSize = bestFittingFontSize(for: text, in: bounds, fontDescriptor: fontDescriptor, additionalAttributes: additionalAttributes)
// TODO: Safely unwrap this later
return NSFont(descriptor: fontDescriptor, size: bestSize)!
}
}
extension NSTextField {
/// Will auto resize the contained text to a font size which fits the frames bounds.
/// Uses the pre-set font to dynamically determine the proper sizing
func fitTextToBounds() {
guard let currentFont = font else {
return
}
let text = stringValue
let bestFittingFont = NSFont.bestFittingFont(for: text, in: bounds, fontDescriptor: currentFont.fontDescriptor, additionalAttributes: basicStringAttributes)
font = bestFittingFont
}
private var basicStringAttributes: [NSAttributedString.Key: Any] {
var attribs = [NSAttributedString.Key: Any]()
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = self.alignment
paragraphStyle.lineBreakMode = self.lineBreakMode
attribs[.paragraphStyle] = paragraphStyle
return attribs
}
}
For me label.adjustsFontSizeToFitWidth = true reduces the font size.
with...
lazy var labelContainerView: UIView =
{ let view = UIView()
return view.labelContainerView(view: view, label) }()
extension UIView {
func anchor( top: NSLayoutYAxisAnchor?,
left: NSLayoutXAxisAnchor?,
bottom: NSLayoutYAxisAnchor?,
right: NSLayoutXAxisAnchor?,
paddingTop: CGFloat,
paddingLeft: CGFloat,
paddingBottom: CGFloat,
paddingRight: CGFloat,
width: CGFloat,
height: CGFloat )
{ translatesAutoresizingMaskIntoConstraints = false
if let top = top { self.topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true }
if let left = left { self.leftAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true }
if let bottom = bottom { self.bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true }
if let right = right { self.rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true }
if width != 0 { widthAnchor.constraint(equalToConstant: width).isActive = true }
if height != 0 { heightAnchor.constraint(equalToConstant: height).isActive = true }
}
}
func labelContainerView(view: UIView, _ label: UILabel) -> UIView
{ view.addSubview(label)
label.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: nil, right: nil, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
return view
}
}
© 2022 - 2024 — McMap. All rights reserved.