うん、かっこいいmyLabel.adjustsFontSizeToFitWidth = YES;
プロパティ。ただし、ラベルが2行以上になるとすぐに、テキストのサイズは変更されません。したがって、四角形に収まらない場合は...で切り捨てられます。
それを行う別の方法はありますか?
ラベルが幅と高さの両方で長方形に収まるようにしたい場合は、ラベルの異なるフォントサイズを試して、収まるかどうかを確認できます。
このスニペットは300 ptから始まり、フォントサイズを小さくすることにより、ターゲットの四角形にラベルを合わせようとします。
- (void) sizeLabel: (UILabel *) label toRect: (CGRect) labelRect {
// Set the frame of the label to the targeted rectangle
label.frame = labelRect;
// Try all font sizes from largest to smallest font size
int fontSize = 300;
int minFontSize = 5;
// Fit label width wize
CGSize constraintSize = CGSizeMake(label.frame.size.width, MAXFLOAT);
do {
// Set current font size
label.font = [UIFont fontWithName:label.font.fontName size:fontSize];
// Find label size for current font size
CGRect textRect = [[label text] boundingRectWithSize:constraintSize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName: label.font}
context:nil];
CGSize labelSize = textRect.size;
// Done, if created label is within target size
if( labelSize.height <= label.frame.size.height )
break;
// Decrease the font size and try again
fontSize -= 2;
} while (fontSize > minFontSize);
}
上記は何が起こっているのかを説明していると思います。より高速な実装では、次のようにキャッシングとargarciansバイナリ検索を使用できます
+ (CGFloat) fontSizeForString: (NSString*) s inRect: (CGRect) labelRect {
// Cache repeat queries
static NSMutableDictionary* mutableDict = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
mutableDict = [NSMutableDictionary dictionary];
});
NSString* key = [NSString stringWithFormat:@"%@_%d_%d", s, (int) labelRect.size.width, (int) labelRect.size.height];
NSNumber* value = [mutableDict objectForKey:key];
if (value)
return value.doubleValue;
// Set the frame of the label to the targeted rectangle
UILabel* label = [[UILabel alloc] init];
label.text = s;
label.frame = labelRect;
// Hopefully between 5 and 300
CGFloat theSize = (CGFloat) [self binarySearchForFontSizeForLabel:label withMinFontSize:5 withMaxFontSize:300 withSize:label.frame.size];
[mutableDict setObject:@(theSize) forKey:key];
return theSize;
}
+ (NSInteger)binarySearchForFontSizeForLabel:(UILabel *)label withMinFontSize:(NSInteger)minFontSize withMaxFontSize:(NSInteger)maxFontSize withSize:(CGSize)size {
// If the sizes are incorrect, return 0, or error, or an assertion.
if (maxFontSize < minFontSize) {
return maxFontSize;
}
// Find the middle
NSInteger fontSize = (minFontSize + maxFontSize) / 2;
// Create the font
UIFont *font = [UIFont fontWithName:label.font.fontName size:fontSize];
// Create a constraint size with max height
CGSize constraintSize = CGSizeMake(size.width, MAXFLOAT);
// Find label size for current font size
CGRect rect = [label.text boundingRectWithSize:constraintSize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName : font}
context:nil];
CGSize labelSize = rect.size;
// EDIT: The next block is modified from the original answer posted in SO to consider the width in the decision. This works much better for certain labels that are too thin and were giving bad results.
if (labelSize.height >= (size.height + 10) && labelSize.width >= (size.width + 10) && labelSize.height <= (size.height) && labelSize.width <= (size.width)) {
return fontSize;
} else if (labelSize.height > size.height || labelSize.width > size.width) {
return [self binarySearchForFontSizeForLabel:label withMinFontSize:minFontSize withMaxFontSize:fontSize - 1 withSize:size];
} else {
return [self binarySearchForFontSizeForLabel:label withMinFontSize:fontSize + 1 withMaxFontSize:maxFontSize withSize:size];
}
}
Nielsの答えがこの問題の最良の答えであることがわかりました。しかし、テキストを合わせる必要がある場所に100個のラベルを付けることができるUIViewがあるので、このプロセスは非常に非効率的であり、パフォーマンスへの影響を感じることができました。
これは、線形検索ではなくバイナリ検索を使用するように変更された彼のコードです。今では非常に効率的に動作します。
- (NSInteger)binarySearchForFontSizeForLabel:(UILabel *)label withMinFontSize:(NSInteger)minFontSize withMaxFontSize:(NSInteger)maxFontSize withSize:(CGSize)size {
// If the sizes are incorrect, return 0, or error, or an assertion.
if (maxFontSize < minFontSize) {
return 0;
}
// Find the middle
NSInteger fontSize = (minFontSize + maxFontSize) / 2;
// Create the font
UIFont *font = [UIFont fontWithName:label.font.fontName size:fontSize];
// Create a constraint size with max height
CGSize constraintSize = CGSizeMake(size.width, MAXFLOAT);
// Find label size for current font size
CGRect rect = [label.text boundingRectWithSize:constraintSize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName : font}
context:nil];
CGSize labelSize = rect.size;
// EDIT: The next block is modified from the original answer posted in SO to consider the width in the decision. This works much better for certain labels that are too thin and were giving bad results.
if (labelSize.height >= (size.height + 10) && labelSize.width >= (size.width + 10) && labelSize.height <= (size.height) && labelSize.width <= (size.width)) {
return fontSize;
} else if (labelSize.height > size.height || labelSize.width > size.width) {
return [self binarySearchForFontSizeForLabel:label withMinFontSize:minFontSize withMaxFontSize:fontSize - 1 withSize:size];
} else {
return [self binarySearchForFontSizeForLabel:label withMinFontSize:fontSize + 1 withMaxFontSize:maxFontSize withSize:size];
}
}
- (void)sizeBinaryLabel:(UILabel *)label toRect:(CGRect)labelRect {
// Set the frame of the label to the targeted rectangle
label.frame = labelRect;
// Try all font sizes from largest to smallest font
int maxFontSize = 300;
int minFontSize = 5;
NSInteger size = [self binarySearchForFontSizeForLabel:label withMinFontSize:minFontSize withMaxFontSize:maxFontSize withSize:label.frame.size];
label.font = [UIFont fontWithName:label.font.fontName size:size];
}
クレジットは https://Gist.github.com/988219 にも適用されます
Swiftバイナリ検索を使用した@NielsCastleの回答によるバージョンです。
extension UILabel{
func adjustFontSizeToFitRect(rect : CGRect){
if text == nil{
return
}
frame = rect
let maxFontSize: CGFloat = 100.0
let minFontSize: CGFloat = 5.0
var q = Int(maxFontSize)
var p = Int(minFontSize)
let constraintSize = CGSize(width: rect.width, height: CGFloat.max)
while(p <= q){
let currentSize = (p + q) / 2
font = font.fontWithSize( CGFloat(currentSize) )
let text = NSAttributedString(string: self.text!, attributes: [NSFontAttributeName:font])
let textRect = text.boundingRectWithSize(constraintSize, options: .UsesLineFragmentOrigin, context: nil)
let labelSize = textRect.size
if labelSize.height < frame.height && labelSize.height >= frame.height-10 && labelSize.width < frame.width && labelSize.width >= frame.width-10 {
break
}else if labelSize.height > frame.height || labelSize.width > frame.width{
q = currentSize - 1
}else{
p = currentSize + 1
}
}
}
}
使用法
label.adjustFontSizeToFitRect(rect)
しばしばただ
label.adjustFontSizeToFitRect(rect.frame)
このソリューション( this answer に基づく)は自動レイアウトで動作し、バイナリ検索を実行して最適なフォントサイズを見つけます。
私が見つけた唯一の警告は、行数を指定できないことです(私の知る限り、必要な行数をboundingRectWithSize
に伝えることができないため)。
AdjustableLabel.h
#import <UIKit/UIKit.h>
@interface AdjustableLabel : UILabel
/**
If set to YES, font size will be automatically adjusted to frame.
Note: numberOfLines can't be specified so it will be set to 0.
*/
@property(nonatomic) BOOL adjustsFontSizeToFitFrame;
@end
AdjustableLabel.m
#import "AdjustableLabel.h"
@interface AdjustableLabel ()
@property(nonatomic) BOOL fontSizeAdjusted;
@end
// The size found S satisfies: S fits in the frame and and S+DELTA doesn't.
#define DELTA 0.5
@implementation AdjustableLabel
- (void)setAdjustsFontSizeToFitFrame:(BOOL)adjustsFontSizeToFitFrame
{
_adjustsFontSizeToFitFrame = adjustsFontSizeToFitFrame;
if (adjustsFontSizeToFitFrame) {
self.numberOfLines = 0; // because boundingRectWithSize works like this was 0 anyway
}
}
- (void)layoutSubviews
{
[super layoutSubviews];
if (self.adjustsFontSizeToFitFrame && !self.fontSizeAdjusted)
{
self.fontSizeAdjusted = YES; // to avoid recursion, because adjustFontSizeToFrame will trigger this method again
[self adjustFontSizeToFrame];
}
}
- (void) adjustFontSizeToFrame
{
UILabel* label = self;
if (label.text.length == 0) return;
// Necessary or single-char texts won't be correctly adjusted
BOOL checkWidth = label.text.length == 1;
CGSize labelSize = label.frame.size;
// Fit label width-wise
CGSize constraintSize = CGSizeMake(checkWidth ? MAXFLOAT : labelSize.width, MAXFLOAT);
// Try all font sizes from largest to smallest font size
CGFloat maxFontSize = 300;
CGFloat minFontSize = 5;
NSString* text = label.text;
UIFont* font = label.font;
while (true)
{
// Binary search between min and max
CGFloat fontSize = (maxFontSize + minFontSize) / 2;
// Exit if approached minFontSize enough
if (fontSize - minFontSize < DELTA/2) {
font = [UIFont fontWithName:font.fontName size:minFontSize];
break; // Exit because we reached the biggest font size that fits
} else {
font = [UIFont fontWithName:font.fontName size:fontSize];
}
// Find label size for current font size
CGRect rect = [text boundingRectWithSize:constraintSize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName : font}
context:nil];
// Now we discard a half
if( rect.size.height <= labelSize.height && (!checkWidth || rect.size.width <= labelSize.width) ) {
minFontSize = fontSize; // the best size is in the bigger half
} else {
maxFontSize = fontSize; // the best size is in the smaller half
}
}
label.font = font;
}
@end
用途
AdjustableLabel* label = [[AdjustableLabel alloc] init];
label.adjustsFontSizeToFitFrame = YES;
// In case you change the font, the size you set doesn't matter
label.font = [UIFont fontWithName:@"OpenSans-Light" size:20];
これは、UILabelのSwift拡張機能です。これは、バイナリ検索アルゴリズムを実行してラベルのフォントと境界のサイズを変更し、iOS 12で動作するようにテストされています。
SAGE: 100x100のサイズに合うようにフォントのサイズを変更し(1.0フォントポイント内で正確)、フォントを上に揃えます。
let adjustedSize = <label>.fitFontForSize(CGSizeMake(100, 100))
<label>.frame = CGRect(x: 0, y: 0, width: 100, height: adjustedSize.height)
以下をファイルにコピー/貼り付け:
extension UILabel {
@discardableResult func fitFontForSize(_ constrainedSize: CGSize,
maxFontSize: CGFloat = 100,
minFontSize: CGFloat = 5,
accuracy: CGFloat = 1) -> CGSize {
assert(maxFontSize > minFontSize)
var minFontSize = minFontSize
var maxFontSize = maxFontSize
var fittingSize = constrainedSize
while maxFontSize - minFontSize > accuracy {
let midFontSize: CGFloat = ((minFontSize + maxFontSize) / 2)
font = font.withSize(midFontSize)
fittingSize = sizeThatFits(constrainedSize)
if fittingSize.height <= constrainedSize.height
&& fittingSize.width <= constrainedSize.width {
minFontSize = midFontSize
} else {
maxFontSize = midFontSize
}
}
return fittingSize
}
}
この関数されませんラベルサイズを変更します。影響を受けるのはfont
プロパティのみです。返されたサイズ値を使用して、ラベルのレイアウトを調整できます。
誰かがMonoTouch/Xamarin.iOS実装を探している場合、私がしたように...ここに行きます:
private int BinarySearchForFontSizeForText(NSString text, int minFontSize, int maxFontSize, SizeF size)
{
if (maxFontSize < minFontSize)
return minFontSize;
int fontSize = (minFontSize + maxFontSize) / 2;
UIFont font = UIFont.BoldSystemFontOfSize(fontSize);
var constraintSize = new SizeF(size.Width, float.MaxValue);
SizeF labelSize = text.StringSize(font, constraintSize, UILineBreakMode.WordWrap);
if (labelSize.Height >= size.Height + 10 && labelSize.Width >= size.Width + 10 && labelSize.Height <= size.Height && labelSize.Width <= size.Width)
return fontSize;
else if (labelSize.Height > size.Height || labelSize.Width > size.Width)
return BinarySearchForFontSizeForText(text, minFontSize, fontSize - 1, size);
else
return BinarySearchForFontSizeForText(text, fontSize + 1, maxFontSize, size);
}
private void SizeLabelToRect(UILabel label, RectangleF labelRect)
{
label.Frame = labelRect;
int maxFontSize = 300;
int minFontSize = 5;
int size = BinarySearchForFontSizeForText(new NSString(label.Text), minFontSize, maxFontSize, label.Frame.Size);
label.Font = UIFont.SystemFontOfSize(size);
}
これはObjective-CからC#へのagarcianのコードの変換であり、小さな変更が加えられています。結果が常に0であるため( borkedのコメントを参照 )計算されたminFontSizeを返します。正しいフォントサイズ。
これらはすべて、元の問題に対する興味深い解決策ですが、それらすべてにも重要なものが欠けています。ファミリーネームだけに頼ってnextフォントをテストすると、重量情報が失われますスモールキャップ、フィギュアスタイルなどのより高度な属性.
より良い方法は、フォント名を渡して[UIFont fontWithName:someFontName size:someFontSize]
、UIFontDescriptor
オブジェクトを渡してから[UIFont fontWithDescriptor:someFontDescriptor size:someFontSize]
。
また、myLabel.numberOfLines = 10
または必要な最大行数に設定します。
上記の答えを使用してもすべてのニーズに対応する実用的なソリューションが見つからなかったため、次の機能を提供する独自のコンポーネントを作成しました:FittableFontLabel
NSAttributedStrings
および基本的な文字列のサポートあなたのいずれかが興味を持っている場合、それは完全なSwift CocoaPodsを使用して利用可能なライブラリです: https://github.com/tbaranes/FittableFontLabel
this の回答に基づくSwift 3の「バイナリ検索ソリューション」は、マイナーな改良を加えたものです。サンプルは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)
}
}
誰かのお役に立てば幸いです。
@agarcianの回答に基づいてUILabelのカテゴリを作成しました。しかし、テキストを描画するために画面上で必要な正方形に応じてfontSizeを計算します。この方法はループを必要とせず、計算は1回の反復で行われます。
ここで.hファイル:
// UILabel+Extended.h
// Created by Firuz on 16/08/14.
// Copyright (c) 2014. All rights reserved.
#import <UIKit/UIKit.h>
@interface UILabel (Extended)
/** This method calculate the optimal font size for current number of lines in UILable. Mus be called after drawing UILabel view */
- (NSInteger)fontSizeWithMinFontSize:(NSInteger)minFontSize withMaxFontSize:(NSInteger)maxFontSize;
@end
そしてここに.mファイル:
// UILabel+Extended.m
// Created by Firuz on 16/08/14.
// Copyright (c) 2014. All rights reserved.
#import "UILabel+Extended.h"
@implementation UILabel (Extended)
- (NSInteger)fontSizeWithMinFontSize:(NSInteger)minFontSize withMaxFontSize:(NSInteger)maxFontSize
{
if (maxFontSize < minFontSize) {
return 0;
}
UIFont *font = [UIFont fontWithName:self.font.fontName size:maxFontSize];
CGFloat lineHeight = [font lineHeight];
CGSize constraintSize = CGSizeMake(MAXFLOAT, lineHeight);
CGRect rect = [self.text boundingRectWithSize:constraintSize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName : font}
context:nil];
CGFloat labelSqr = self.frame.size.width * self.frame.size.height;
CGFloat stringSqr = rect.size.width/self.frame.size.width * (lineHeight + font.pointSize) * self.frame.size.width;
CGFloat multiplyer = labelSqr/stringSqr;
if (multiplyer < 1) {
if (minFontSize < maxFontSize*multiplyer) {
return maxFontSize * multiplyer;
} else {
return minFontSize;
}
}
return maxFontSize;
}
@end
すべてのバイナリ検索は適切ですが、フレームチェックを使用して再帰を停止するのはそれほど論理的ではありません。フォントサイズをより適切に確認します。UIFontが浮動小数点サイズをサポートしているため、このフォントの方が適しています。さらに、ラベルの段落スタイルを使用して、サイズを正確に計算します。
興味深い人がいれば、次のコードを見ることができます。
static UIFont * ___suitableFontInRangePrivate(const CGSize labelSize,
NSParagraphStyle * paragraphStyle,
NSString * fontName,
NSString * text,
const CGFloat minSize,
const CGFloat maxSize)
{
// Font size in range, middle size between max & min.
const CGFloat currentSize = minSize + ((maxSize - minSize) / 2);
// Font with middle size.
UIFont * currentFont = [UIFont fontWithName:fontName size:currentSize];
// Calculate text height.
const CGFloat textHeight = [text boundingRectWithSize:CGSizeMake(labelSize.width, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{ NSFontAttributeName : currentFont, NSParagraphStyleAttributeName : paragraphStyle }
context:nil].size.height;
CGFloat min, max;
if (textHeight > labelSize.height)
{
// Take left range part.
min = minSize;
max = currentSize;
}
else
{
// Take right range part.
min = currentSize;
max = maxSize;
}
// If font size in int range [0.0; 2.0] - got it, othervice continue search.
return ((max - min) <= 2.0) ? currentFont : ___suitableFontInRangePrivate(labelSize, paragraphStyle, fontName, text, min, max);
}
void UILabelAdjustsFontSizeToFrame(UILabel * label)
{
if (!label) return;
NSString * text = [label text];
__block NSParagraphStyle * style = nil;
[[label attributedText] enumerateAttributesInRange:NSMakeRange(0, [text length])
options:(NSAttributedStringEnumerationOptions)0
usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop){
id paragraphStyle = [attrs objectForKey:@"NSParagraphStyle"];
if (paragraphStyle) style = [paragraphStyle retain];
}];
if (!style)
{
NSMutableParagraphStyle * paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
if (!paragraphStyle) paragraphStyle = [[NSMutableParagraphStyle alloc] init];
if (paragraphStyle)
{
[paragraphStyle setLineBreakMode:[label lineBreakMode]];
[paragraphStyle setAlignment:[label textAlignment]];
}
style = paragraphStyle;
}
UIFont * suitableFont = ___suitableFontInRangePrivate([label frame].size, style, [[label font] fontName], text, 0, 500);
[label setFont:suitableFont];
[style release];
}
@agarcianの答えは近いものでしたが、私にはうまくいきませんでした。他の誰かがコメントで述べたように、常に0を返しました。
これが私の試みです。
乾杯!
/**
* Returns the font size required in order to fit the specified text in the specified area.
* NB! When drawing, be sure to pass in the same options that we pass to boundingRectWithSize:options:attributes:context:
* Heavily modified form of: http://stackoverflow.com/a/14662750/1027452
*/
+(NSInteger)fontSizeForText:(NSString *)text withFont:(UIFont *)font inArea:(CGSize)areaSize minFontSize:(NSInteger)minFontSize maxFontSize:(NSInteger)maxFontSize
{
// If the sizes are incorrect, return 0, or error, or an assertion.
if (maxFontSize < minFontSize) {
return 0;
}
// Find the middle
NSInteger fontSize = (minFontSize + maxFontSize) / 2;
// Create the font
UIFont *f = [UIFont fontWithName:font.fontName size:fontSize];
// Create a constraint size with max height
CGSize constraintSize = CGSizeMake(areaSize.width, MAXFLOAT);
// Find label size for current font size
CGRect rect = [text boundingRectWithSize:constraintSize
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes:@{NSFontAttributeName : f}
context:nil];
CGSize labelSize = rect.size;
if (labelSize.height <= areaSize.height && labelSize.width <= areaSize.width )
{
return fontSize;
}
else if (labelSize.height > areaSize.height || labelSize.width > areaSize.width)
{
return [self fontSizeForText:text withFont:f inArea:areaSize minFontSize:minFontSize maxFontSize:maxFontSize -1];;
}
else
{
return [self fontSizeForText:text withFont:f inArea:areaSize minFontSize:minFontSize+1 maxFontSize:maxFontSize];;
}
}
ニールズ城のコードワークを見つける。
これは、実装が異なる同じアイデアです。
私のソリューションはより正確ですが、CPUをより集中的に使用します。
UILabelを継承するクラスにこの関数を追加します。
-(void)fitCurrentFrame{
CGSize iHave = self.frame.size;
BOOL isContained = NO;
do{
CGSize iWant = [self.text sizeWithFont:self.font];
if(iWant.width > iHave.width || iWant.height > iHave.height){
self.font = [UIFont fontWithName:self.font.fontName size:self.font.pointSize - 0.1];
isContained = NO;
}else{
isContained = YES;
}
}while (isContained == NO);
}