问题
I have some text which I am drawing into a fixed frame via an NSAttributedString (code below). At the moment I am hard coding the text size to 16. My question is, is there a way to calculate the best fit size for the text for the given frame ?
- (void)drawText:(CGContextRef)contextP startX:(float)x startY:(float)
y withText:(NSString *)standString
{
CGContextTranslateCTM(contextP, 0, (bottom-top)*2);
CGContextScaleCTM(contextP, 1.0, -1.0);
CGRect frameText = CGRectMake(1, 0, (right-left)*2, (bottom-top)*2);
NSMutableAttributedString * attrString = [[NSMutableAttributedString alloc] initWithString:standString];
[attrString addAttribute:NSFontAttributeName
value:[UIFont fontWithName:@"Helvetica-Bold" size:16.0]
range:NSMakeRange(0, attrString.length)];
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)(attrString));
struct CGPath * p = CGPathCreateMutable();
CGPathAddRect(p, NULL, frameText);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0,0), p, NULL);
CTFrameDraw(frame, contextP);
}
回答1:
The only way I can see this being possible is to have a system that runs the size calculation then adjusts the size and repeats until it finds the right size.
I.e. set up a bisecting algorithm that goes between certain sizes.
i.e. run it for size 10. Too small. Size 20. Too small. Size 30. Too big. Size 25. Too small. Size 27. Just right, use size 27.
You could even start in hundreds.
Size 100. Too big. Size 50. etc...
回答2:
Here is a simple piece of code that will figure out the maximum font size to fit within the bounds of a frame:
UILabel *label = [[UILabel alloc] initWithFrame:frame];
label.text = @"Some text";
float largestFontSize = 12;
while ([label.text sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:largestFontSize]}].width > modifierFrame.size.width)
{
largestFontSize--;
}
label.font = [UIFont systemFontOfSize:largestFontSize];
回答3:
The currently accepted answer talks of an algorithm, but iOS provides calculations for an NSString object.
I would use sizeWithAttributes:
of the NSString
class.
sizeWithAttributes:
Returns the bounding box size the receiver occupies when drawn with the given attributes.
- (CGSize)sizeWithAttributes:(NSDictionary *)attributes
Source: Apple Docs - NSString UIKit Additions Reference
EDIT Misinterpreted the question, so this answer is off the mark.
回答4:
You could use sizeWithFont :
[myString sizeWithFont:[UIFont fontWithName:@"HelveticaNeue-Light" size:24]
constrainedToSize:CGSizeMake(293, 10000)] // put the size of your frame
But it is deprecated in iOS 7, so I recommend if working with string in UILabel :
[string sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:17.0f]}];
If you are working with a rect :
CGRect textRect = [text boundingRectWithSize:mySize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName:FONT}
context:nil];
CGSize size = textRect.size;
回答5:
You can set the UILabel
's property adjustsFontSizeToFitWidth
to YES
as per Apple's documentation
回答6:
Even more easy/faster (but of course approximate) way would be this:
class func calculateOptimalFontSize(textLength:CGFloat, boundingBox:CGRect) -> CGFloat
{
let area:CGFloat = boundingBox.width * boundingBox.height
return sqrt(area / textLength)
}
We are assuming each char is N x N pixels, so we just calculate how many times N x N goes inside bounding box.
回答7:
A little trick helps to make use of sizeWithAttributes:
without the need of iterating for the right result:
NSSize sampleSize = [wordString sizeWithAttributes:
@{ NSFontAttributeName: [NSFont fontWithName:fontName size:fontSize] }];
CGFloat ratio = rect.size.width / sampleSize.width;
fontSize *= ratio;
Make sure the fontSize
for the sample is big enough to get good results.
回答8:
Here is code which will do exactly that: calculate optimal font size within some bounds. This sample is in context of UITextView
subclass, so it's using its bounds as a "given frame":
func binarySearchOptimalFontSize(min: Int, max: Int) -> Int {
let middleSize = (min + max) / 2
if min > max {
return middleSize
}
let middleFont = UIFont(name: font!.fontName, size: CGFloat(middleSize))!
let attributes = [NSFontAttributeName : middleFont]
let attributedString = NSAttributedString(string: text, attributes: attributes)
let size = CGSize(width: bounds.width, height: .greatestFiniteMagnitude)
let options: NSStringDrawingOptions = [.usesLineFragmentOrigin, .usesFontLeading]
let textSize = attributedString.boundingRect(with: size, options: options, context: nil)
if textSize.size.equalTo(bounds.size) {
return middleSize
} else if (textSize.height > bounds.size.height || textSize.width > bounds.size.width) {
return binarySearchOptimalFontSize(min: min, max: middleSize - 1)
} else {
return binarySearchOptimalFontSize(min: middleSize + 1, max: max)
}
}
I hope that helps.
回答9:
This is the code to have dynamic font size changing by the frame width, using the logic from the other answers. The while loop might be dangerous, so please donot hesitate to submit improvements.
float fontSize = 17.0f; //initial font size
CGSize rect;
while (1) {
fontSize = fontSize+0.1;
rect = [watermarkText sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:fontSize]}];
if ((int)rect.width == (int)subtitle1Text.frame.size.width) {
break;
}
}
subtitle1Text.fontSize = fontSize;
回答10:
Here's a method that seems to work well for iOS 9 using UITextView
objects. You might have to tweet it a bit for other applications.
/*!
* Find the height of the smallest rectangle that will enclose a string using the given font.
*
* @param string The string to check.
* @param font The drawing font.
* @param width The width of the drawing area.
*
* @return The height of the rectngle enclosing the text.
*/
- (float) heightForText: (NSString *) string font: (UIFont *) font width: (float) width {
NSDictionary *fontAttributes = [NSDictionary dictionaryWithObject: font
forKey: NSFontAttributeName];
CGRect rect = [string boundingRectWithSize: CGSizeMake(width, INT_MAX)
options: NSStringDrawingUsesLineFragmentOrigin
attributes: fontAttributes
context: nil];
return rect.size.height;
}
/*!
* Find the largest font size that will allow a block of text to fit in a rectangle of the given size using the system
* font.
*
* The code is tested and optimized for UITextView objects.
*
* The font size is determined to ±0.5. Change delta in the code to get more or less precise results.
*
* @param string The string to check.
* @param size The size of the bounding rectangle.
*
* @return: The font size.
*/
- (float) maximumSystemFontSize: (NSString *) string size: (CGSize) size {
// Hack: For UITextView, the last line is clipped. Make sure it's not one we care about.
if ([string characterAtIndex: string.length - 1] != '\n') {
string = [string stringByAppendingString: @"\n"];
}
string = [string stringByAppendingString: @"M\n"];
float maxFontSize = 16.0;
float maxHeight = [self heightForText: string font: [UIFont systemFontOfSize: maxFontSize] width: size.width];
while (maxHeight < size.height) {
maxFontSize *= 2.0;
maxHeight = [self heightForText: string font: [UIFont systemFontOfSize: maxFontSize] width: size.width];
}
float minFontSize = maxFontSize/2.0;
float minHeight = [self heightForText: string font: [UIFont systemFontOfSize: minFontSize] width: size.width];
while (minHeight > size.height) {
maxFontSize = minFontSize;
minFontSize /= 2.0;
maxHeight = minHeight;
minHeight = [self heightForText: string font: [UIFont systemFontOfSize: minFontSize] width: size.width];
}
const float delta = 0.5;
while (maxFontSize - minFontSize > delta) {
float middleFontSize = (minFontSize + maxFontSize)/2.0;
float middleHeight = [self heightForText: string font: [UIFont systemFontOfSize: middleFontSize] width: size.width];
if (middleHeight < size.height) {
minFontSize = middleFontSize;
minHeight = middleHeight;
} else {
maxFontSize = middleFontSize;
maxHeight = middleHeight;
}
}
return minFontSize;
}
回答11:
I like the approach given by @holtwick, but found that it would sometimes overestimate what would fit. I created a tweak that seems to work well in my tests. Tip: Don't forget to test with really wide letters like "WWW" or even "௵௵௵"
func idealFontSize(for text: String, font: UIFont, width: CGFloat) -> CGFloat {
let baseFontSize = CGFloat(256)
let textSize = text.size(attributes: [NSFontAttributeName: font.withSize(baseFontSize)])
let ratio = width / textSize.width
let ballparkSize = baseFontSize * ratio
let stoppingSize = ballparkSize / CGFloat(2) // We don't want to loop forever, if we've already come down to 50% of the ballpark size give up
var idealSize = ballparkSize
while (idealSize > stoppingSize && text.size(attributes: [NSFontAttributeName: font.withSize(idealSize)]).width > width) {
// We subtract 0.5 because sometimes ballparkSize is an overestimate of a size that will fit
idealSize -= 0.5
}
return idealSize
}
回答12:
Here is my solution in swift 4:
private func adjustedFontSizeOf(label: UILabel) -> CGFloat {
guard let textSize = label.text?.size(withAttributes: [.font: label.font]), textSize.width > label.bounds.width else {
return label.font.pointSize
}
let scale = label.bounds.width / textSize.width
let actualFontSize = scale * label.font.pointSize
return actualFontSize
}
I hope it helps someone.
来源:https://stackoverflow.com/questions/20468641/calculate-font-size-to-fit-frame-core-text-nsattributedstring-ios