J'ai du texte que je dessine dans un cadre fixe via un NSAttributedString (code ci-dessous). Pour le moment, je suis en train de coder la taille du texte à 16. Ma question est la suivante: existe-t-il un moyen de calculer la taille la mieux adaptée au texte pour le cadre donné?
- (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);
}
La seule façon pour moi de voir cela est possible est d’avoir un système qui exécute le calcul de la taille, puis ajuste la taille et se répète jusqu’à ce qu’il trouve la bonne taille.
C'est à dire. mettre en place un algorithme de bissection qui va entre certaines tailles.
c.-à-d. utilisez-le pour la taille 10 . Trop petit .. Taille 20 . Trop petit .. Taille 30 . Trop grand .. Taille 25 . Trop petit . Taille 27 . Parfait, utilisez la taille 27.
Vous pouvez même commencer par centaines.
Taille 100 . Trop grand . Taille 50 . Etc ...
Voici un simple morceau de code qui déterminera la taille de police maximum pour s’inscrire dans les limites d’un cadre:
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];
La réponse actuellement acceptée parle d'un algorithme, mais iOS fournit des calculs pour un objet NSString. J'utiliserais sizeWithAttributes:
de la classe NSString
.
sizeWithAttributes:
Renvoie la taille de la boîte englobante occupée par le destinataire lorsqu'il est tracé avec les attributs donnés.
- (CGSize)sizeWithAttributes:(NSDictionary *)attributes
Source: Apple Docs - Référence des compléments UIKit NSString
EDIT A mal interprété la question, cette réponse est donc fausse.
Une petite astuce permet d’utiliser sizeWithAttributes:
sans avoir à itérer pour obtenir le bon résultat:
NSSize sampleSize = [wordString sizeWithAttributes:
@{ NSFontAttributeName: [NSFont fontWithName:fontName size:fontSize] }];
CGFloat ratio = rect.size.width / sampleSize.width;
fontSize *= ratio;
Assurez-vous que la variable fontSize
de l'échantillon est suffisamment grande pour obtenir de bons résultats.
Vous pouvez utiliser sizeWithFont:
[myString sizeWithFont:[UIFont fontWithName:@"HelveticaNeue-Light" size:24]
constrainedToSize:CGSizeMake(293, 10000)] // put the size of your frame
Mais il est obsolète dans iOS 7, je recommande donc de travailler avec des chaînes dans UILabel:
[string sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:17.0f]}];
Si vous travaillez avec un rect:
CGRect textRect = [text boundingRectWithSize:mySize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName:FONT}
context:nil];
CGSize size = textRect.size;
Voici le code qui fera exactement cela: calculer la taille de police optimale dans certaines limites. Cet exemple est dans le contexte de la sous-classe UITextView
et utilise donc ses limites comme "image donnée":
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)
}
}
J'espère que ça aide.
Vous pouvez définir la propriété UILabel
de adjustsFontSizeToFitWidth
sur YES
conformément à documentation d'Apple
Encore plus facile/rapide (mais bien sûr approximatif), voici ce qui suit:
class func calculateOptimalFontSize(textLength:CGFloat, boundingBox:CGRect) -> CGFloat
{
let area:CGFloat = boundingBox.width * boundingBox.height
return sqrt(area / textLength)
}
Nous supposons que chaque caractère est N x N pixels, nous calculons donc simplement le nombre de fois où N x N passe dans le cadre de sélection.
C'est le code pour que la taille de police dynamique change en fonction de la largeur du cadre, en utilisant la logique des autres réponses. La boucle while peut être dangereuse, n'hésitez donc pas à soumettre des améliorations.
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;
J'aime l'approche proposée par @holtwick, mais j'ai constaté qu'elle surestimait parfois ce qui conviendrait. J'ai créé un Tweak qui semble bien fonctionner lors de mes tests. Astuce: N'oubliez pas de tester avec des lettres très larges comme "WWW" ou même ""
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
}
Voici une méthode qui semble bien fonctionner pour iOS 9 utilisant des objets UITextView
. Vous pourriez avoir à le tweet un peu pour d'autres 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;
}
Voici ma solution dans 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
}
J'espère que ça aide quelqu'un.