NSAttributedStringを介して固定フレームに描画しているテキストがあります(以下のコード)。現在、テキストサイズを16にハードコーディングしています。私の質問は、指定されたフレームのテキストに最適なサイズを計算する方法はありますか?
- (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);
}
これが可能であることがわかる唯一の方法は、サイズ計算を実行し、サイズを調整して、適切なサイズが見つかるまで繰り返すシステムを用意することです。
つまり特定のサイズの間で行く二等分アルゴリズムを設定します。
つまり、サイズ10で実行します。小さすぎます。サイズ20。小さすぎます。サイズ30。大きすぎる。サイズ25。小さすぎます。サイズ27。ちょうどいい、サイズ27を使用します。
数百から始めることもできます。
サイズ100。大きすぎます。サイズ50など...
以下は、フレームの境界内に収まる最大フォントサイズを算出する簡単なコードです。
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];
現在受け入れられている答えはアルゴリズムについて語っていますが、iOSはNSStringオブジェクトの計算を提供します。私は使うだろう sizeWithAttributes:
のNSString
クラス。
sizeWithAttributes:
指定された属性で描画されたときにレシーバーが占有する境界ボックスのサイズを返します。
- (CGSize)sizeWithAttributes:(NSDictionary *)attributes
出典: Apple Docs-NSString UIKit Additions Reference
[〜#〜] edit [〜#〜]質問の解釈を間違えたため、この回答は適切ではありません。
さらに簡単/高速(もちろんおおよそ)の方法は次のようになります。
class func calculateOptimalFontSize(textLength:CGFloat, boundingBox:CGRect) -> CGFloat
{
let area:CGFloat = boundingBox.width * boundingBox.height
return sqrt(area / textLength)
}
各文字はN x Nピクセルであると想定しているため、N x Nがバウンディングボックス内に入る回数を計算するだけです。
SizeWithFontを使用できます:
[myString sizeWithFont:[UIFont fontWithName:@"HelveticaNeue-Light" size:24]
constrainedToSize:CGSizeMake(293, 10000)] // put the size of your frame
ただし、iOS 7では非推奨であるため、UILabelで文字列を操作する場合は以下をお勧めします。
[string sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:17.0f]}];
Rectで作業している場合:
CGRect textRect = [text boundingRectWithSize:mySize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName:FONT}
context:nil];
CGSize size = textRect.size;
UILabel
のプロパティadjustsFontSizeToFitWidth
をYES
に設定できます Appleのドキュメント
正確にこれを行うコードは次のとおりです。いくつかの境界内で最適なフォントサイズを計算します。このサンプルはUITextView
サブクラスのコンテキストにあるため、その境界を「所定のフレーム」として使用しています。
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)
}
}
お役に立てば幸いです。
ちょっとしたトリックがsizeWithAttributes:
正しい結果を得るために反復する必要はありません:
NSSize sampleSize = [wordString sizeWithAttributes:
@{ NSFontAttributeName: [NSFont fontWithName:fontName size:fontSize] }];
CGFloat ratio = rect.size.width / sampleSize.width;
fontSize *= ratio;
サンプルのfontSize
が適切な結果を得るのに十分な大きさであることを確認してください。
これは、他の回答のロジックを使用して、フレーム幅によって動的なフォントサイズを変更するコードです。 whileループは危険な場合があるので、改善を送信することをためらわないでください。
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;
私は@holtwickによるアプローチが好きですが、適切なものを過大評価することがあることがわかりました。テストでうまく機能するように見えるTweakを作成しました。ヒント:「WWW」や「௵௵௵」などの本当に広い文字でテストすることを忘れないでください
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
}
以下は、iOS 9でUITextView
オブジェクトを使用してうまく機能するように見えるメソッドです。他のアプリケーションでは少しツイートする必要があるかもしれません。
/*!
* 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;
}
ここに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
}
誰かのお役に立てば幸いです。