web-dev-qa-db-ja.com

フレームに合わせてフォントサイズを計算する-コアテキスト-NSAttributedString-iOS

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

これが可能であることがわかる唯一の方法は、サイズ計算を実行し、サイズを調整して、適切なサイズが見つかるまで繰り返すシステムを用意することです。

つまり特定のサイズの間で行く二等分アルゴリズムを設定します。

つまり、サイズ10で実行します。小さすぎます。サイズ20。小さすぎます。サイズ30。大きすぎる。サイズ25。小さすぎます。サイズ27。ちょうどいい、サイズ27を使用します。

数百から始めることもできます。

サイズ100。大きすぎます。サイズ50など...

9
Fogmeister

以下は、フレームの境界内に収まる最大フォントサイズを算出する簡単なコードです。

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];
23
user3072402

現在受け入れられている答えはアルゴリズムについて語っていますが、iOSはNSStringオブジェクトの計算を提供します。私は使うだろう sizeWithAttributes:NSStringクラス。

sizeWithAttributes:

指定された属性で描画されたときにレシーバーが占有する境界ボックスのサイズを返します。

    - (CGSize)sizeWithAttributes:(NSDictionary *)attributes

出典: Apple Docs-NSString UIKit Additions Reference

[〜#〜] edit [〜#〜]質問の解釈を間違えたため、この回答は適切ではありません。

6
CloakedEddy

さらに簡単/高速(もちろんおおよそ)の方法は次のようになります。

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がバウンディングボックス内に入る回数を計算するだけです。

5
sabiland

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;
4
Pull

UILabelのプロパティadjustsFontSizeToFitWidthYESに設定できます Appleのドキュメント

3
MuhammadBassio

正確にこれを行うコードは次のとおりです。いくつかの境界内で最適なフォントサイズを計算します。このサンプルは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)
    }
}

お役に立てば幸いです。

3
tadija

ちょっとしたトリックがsizeWithAttributes:正しい結果を得るために反復する必要はありません:

NSSize sampleSize = [wordString sizeWithAttributes:
    @{ NSFontAttributeName: [NSFont fontWithName:fontName size:fontSize] }];
CGFloat ratio = rect.size.width / sampleSize.width;
fontSize *= ratio;

サンプルのfontSizeが適切な結果を得るのに十分な大きさであることを確認してください。

3
Holtwick

これは、他の回答のロジックを使用して、フレーム幅によって動的なフォントサイズを変更するコードです。 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;
1
vatti

私は@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
}
0
C. Clark

以下は、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;
}
0
Mike

ここに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
}

誰かのお役に立てば幸いです。

0
lam.nguyen2169