【发布时间】:2012-02-21 23:55:26
【问题描述】:
是否可以在UILabel 的多行上同时使用 autoshrink 属性?例如,2 行可用的大文本。
【问题讨论】:
-
我更进一步回答了您的问题,因此它支持多行、可点击的标签,并且兼容 swift 5。只需在下面寻找我的答案。希望对您有所帮助!
是否可以在UILabel 的多行上同时使用 autoshrink 属性?例如,2 行可用的大文本。
【问题讨论】:
我稍微修改了接受答案的代码,使其成为UILabel 上的一个类别:
头文件:
#import <UIKit/UIKit.h>
@interface UILabel (MultiLineAutoSize)
- (void)adjustFontSizeToFit;
@end
以及实现文件:
@implementation UILabel (MultiLineAutoSize)
- (void)adjustFontSizeToFit
{
UIFont *font = self.font;
CGSize size = self.frame.size;
for (CGFloat maxSize = self.font.pointSize; maxSize >= self.minimumFontSize; maxSize -= 1.f)
{
font = [font fontWithSize:maxSize];
CGSize constraintSize = CGSizeMake(size.width, MAXFLOAT);
CGSize labelSize = [self.text sizeWithFont:font constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];
if(labelSize.height <= size.height)
{
self.font = font;
[self setNeedsLayout];
break;
}
}
// set the font to the minimum size anyway
self.font = font;
[self setNeedsLayout];
}
@end
【讨论】:
if 中的前两行子句是多余的,因为它们将在for 循环下执行。 (2) 如果还需要 UILabel 的文本垂直对齐到顶部(如果它使用的行数少于标签中实际显示的行数),则将标签的高度设置为 labelSize.height .请注意,sizeToFit 不能用于此目的,因为它会破坏以前格式化的文本并将其调整为原始字体大小。
float minFontSize = self.font.pointSize * self.minimumScaleFactor;。然后将>= self.minimumFontSize 替换为>= minFontSize。
是的,这是可能的,尽管一开始很难弄清楚。我将更进一步,向您展示如何点击文本中的任何区域。
使用此方法,您可以拥有 UI 标签:
第 1 步:
使 UILabel 具有“Truncate Tail”的换行符属性并设置最小字体比例。
如果您不熟悉字体比例,请记住以下规则:
minimumFontSize/defaultFontSize = fontscale
在我的例子中,我希望 7.2 成为最小字体大小,而我的起始字体大小是 36。因此,7.2 / 36 = 0.2
第 2 步:
如果您不关心标签的可点击性,而只是想要一个有效的多行标签,那么您就完成了!
但是,如果您希望 标签可点击,请继续阅读...
添加我创建的以下扩展
extension UILabel {
func setOptimalFontSize(maxFontSize:CGFloat,text:String){
let width = self.bounds.size.width
var font_size:CGFloat = maxFontSize //Set the maximum font size.
var stringSize = NSString(string: text).size(withAttributes: [.font : self.font.withSize(font_size)])
while(stringSize.width > width){
font_size = font_size - 1
stringSize = NSString(string: text).size(withAttributes: [.font : self.font.withSize(font_size)])
}
self.font = self.font.withSize(font_size)//Forcefully change font to match what it would be graphically.
}
}
这样使用(只需将<Label> 替换为您的实际标签名称):
<Label>.setOptimalFontSize(maxFontSize: 36.0, text: formula)
这个扩展是必需的,因为自动收缩在标签自动收缩后不会改变标签的'font'属性,所以你必须通过与使用 . size(withAttributes) 函数,它模拟特定字体的大小。
这是必要的,因为检测标签点击位置的解决方案需要知道确切的字体大小。
第 3 步:
添加以下扩展:
extension UITapGestureRecognizer {
func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool {
// Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: CGSize.zero)
let mutableAttribString = NSMutableAttributedString(attributedString: label.attributedText!)
mutableAttribString.addAttributes([NSAttributedString.Key.font: label.font!], range: NSRange(location: 0, length: label.attributedText!.length))
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 6
paragraphStyle.lineBreakMode = .byTruncatingTail
paragraphStyle.alignment = .center
mutableAttribString.addAttributes([.paragraphStyle: paragraphStyle], range: NSMakeRange(0, mutableAttribString.string.count))
let textStorage = NSTextStorage(attributedString: mutableAttribString)
// Configure textContainer
textContainer.lineFragmentPadding = 0.0
textContainer.lineBreakMode = label.lineBreakMode
textContainer.maximumNumberOfLines = label.numberOfLines
// Configure layoutManager and textStorage
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
let labelSize = label.bounds.size
textContainer.size = labelSize
// Find the tapped character location and compare it to the specified range
let locationOfTouchInLabel = self.location(in: label)
let textBoundingBox = layoutManager.usedRect(for: textContainer)
//let textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
//(labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y);
let textContainerOffset = CGPoint(x: (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x, y: (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y)
//let locationOfTouchInTextContainer = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x,
// locationOfTouchInLabel.y - textContainerOffset.y);
let locationOfTouchInTextContainer = CGPoint(x: locationOfTouchInLabel.x - textContainerOffset.x, y: locationOfTouchInLabel.y - textContainerOffset.y)
let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
print("IndexOfCharacter=",indexOfCharacter)
print("TargetRange=",targetRange)
return NSLocationInRange(indexOfCharacter, targetRange)
}
}
您需要针对特定的多行情况修改此扩展。在我的例子中,你会注意到我使用了段落样式。
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 6
paragraphStyle.lineBreakMode = .byTruncatingTail
paragraphStyle.alignment = .center
mutableAttribString.addAttributes([.paragraphStyle: paragraphStyle], range: NSMakeRange(0, mutableAttribString.string.count))
确保在扩展程序中将此更改为您实际使用的行距,以便一切计算正确。
第 4 步:
将gestureRecognizer添加到viewDidLoad中的标签或您认为合适的位置(只需再次将<Label>替换为您的标签名称:
<Label>.addGestureRecognizer(UITapGestureRecognizer(target:self, action: #selector(tapLabel(gesture:))))
这是我的 tapLabel 函数的简化示例(只需将 <Label> 替换为您的 UILabel 名称):
@IBAction func tapLabel(gesture: UITapGestureRecognizer) {
guard let text = <Label>.attributedText?.string else {
return
}
let click_range = text.range(of: "(α/β)")
if gesture.didTapAttributedTextInLabel(label: <Label>, inRange: NSRange(click_range!, in: text)) {
print("Tapped a/b")
}else {
print("Tapped none")
}
}
在我的示例中只是一个注释,我的字符串是BED = N * d * [ RBE + ( d / (α/β) ) ],所以在这种情况下我只是得到了α/β 的范围。您可以在字符串中添加“\n”以添加换行符和您想要的任何文本,然后测试它以在下一行找到一个字符串,它仍然会找到它并正确检测到点击!
就是这样!你完成了。享受多行 可点击标签。
【讨论】:
extension UILabel{
func adjustFont(minSize:Int, maxSize:Int){
var newFont = self.font
for index in stride(from: maxSize, to: minSize, by: -1) {
newFont = UIFont.systemFont(ofSize: CGFloat(index))
let size = CGSize(width: self.frame.width, height: CGFloat(Int.max))
let size2 = (self.text! as NSString).boundingRect(with: size, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [NSAttributedStringKey.font:newFont!], context: nil).size
if size2.height < self.frame.size.height{
break
}
}
self.font = newFont
}
}
您还需要为 UILabel 的 numberOfLines 属性赋值。
【讨论】:
试试这个:
在标签上设置文本属性后,子类 UILabel 或调用 adjustFontSize 方法
override var text : String? { didSet { self.adjustFontSize() } }
func adjustFontSize()
{
var lineCount = self.string.components(separatedBy: "\n").count - 1
var textArray = self.string.components(separatedBy: " ")
var wordsToCompare = 1
while(textArray.count > 0)
{
let words = textArray.first(n: wordsToCompare).joined(separator: " ")
let wordsWidth = words.widthForHeight(0, font: self.font)
if(wordsWidth > self.frame.width)
{
textArray.removeFirst(wordsToCompare)
lineCount += 1
wordsToCompare = 1
}
else if(wordsToCompare > textArray.count)
{
break
}
else
{
wordsToCompare += 1
}
}
self.numberOfLines = lineCount + 1
}
【讨论】:
我使用了@wbarksdale 的 Swift 3 解决方案,但发现中间的长词被截断了。为了保持文字完整,我不得不修改如下:
extension UILabel {
func adjustFontSizeToFit(minimumFontSize: CGFloat, maximumFontSize: CGFloat? = nil) {
let maxFontSize = maximumFontSize ?? font.pointSize
let words = self.text?.components(separatedBy: " ")
var longestWord: String?
if let max = words?.max(by: {$1.characters.count > $0.characters.count}) {
longestWord = max
}
for size in stride(from: maxFontSize, to: minimumFontSize, by: -CGFloat(0.1)) {
let proposedFont = font.withSize(size)
let constraintSize = CGSize(width: bounds.size.width, height: CGFloat(MAXFLOAT))
let labelSize = ((text ?? "") as NSString).boundingRect(with: constraintSize,
options: .usesLineFragmentOrigin,
attributes: [NSFontAttributeName: proposedFont],
context: nil)
let wordConstraintSize = CGSize(width: CGFloat(MAXFLOAT), height: CGFloat(MAXFLOAT))
let longestWordSize = ((longestWord ?? "") as NSString).boundingRect(with: wordConstraintSize,
options: .usesFontLeading,
attributes: [NSFontAttributeName: proposedFont],
context: nil)
if labelSize.height <= bounds.size.height && longestWordSize.width < constraintSize.width {
font = proposedFont
setNeedsLayout()
break
}
}
}
}
【讨论】:
UILabel 的电子邮件列表)。我只是将分隔符从空格更改为换行符(“\n”)并删除了与height 相关的所有内容。谢谢你!
改编自 @DaGaMs 的 swifty 版本。
SWIFT 2:
extension UILabel {
func adjustFontSizeToFit(minimumFontSize: CGFloat, maximumFontSize: CGFloat? = nil) {
let maxFontSize = maximumFontSize ?? font.pointSize
for size in stride(from: maxFontSize, to: minimumFontSize, by: -CGFloat(0.1)) {
let proposedFont = font.fontWithSize(size)
let constraintSize = CGSizeMake(bounds.size.width, CGFloat(MAXFLOAT))
let labelSize = ((text ?? "") as NSString).boundingRectWithSize(constraintSize,
options: .UsesLineFragmentOrigin,
attributes: [NSFontAttributeName: proposedFont],
context: nil)
if labelSize.height <= bounds.size.height {
font = proposedFont
setNeedsLayout()
break;
}
}
}
}
SWIFT 3:
extension UILabel {
func adjustFontSizeToFit(minimumFontSize: CGFloat, maximumFontSize: CGFloat? = nil) {
let maxFontSize = maximumFontSize ?? font.pointSize
for size in stride(from: maxFontSize, to: minimumFontSize, by: -CGFloat(0.1)) {
let proposedFont = font.withSize(size)
let constraintSize = CGSize(width: bounds.size.width, height: CGFloat(MAXFLOAT))
let labelSize = ((text ?? "") as NSString).boundingRect(with: constraintSize,
options: .usesLineFragmentOrigin,
attributes: [NSFontAttributeName: proposedFont],
context: nil)
if labelSize.height <= bounds.size.height {
font = proposedFont
setNeedsLayout()
break;
}
}
}
}
【讨论】:
MontiRabbit 的帖子因名声不佳无法评论,所以我会做出新的回答。他(和她的推荐人)提出的解决方案不适用于 Xcode 7.3 或更高版本,这是不精确的。 为了让它发挥作用,在故事板中,我必须:
希望对您有所帮助! ;)
【讨论】:
对于 UIButton,只有这些行对我有用:
self.centerBtn.titleLabel.numberOfLines = 2;
self.centerBtn.titleLabel.textAlignment = NSTextAlignmentCenter;
self.centerBtn.titleLabel.adjustsFontSizeToFitWidth = YES;
【讨论】:
我已经根据上面“The Dude”的回答在 UILabel 上写了一个小类别来实现这个功能。
【讨论】:
NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin 修改这个要点才能使其工作。
我找到了这个链接http://beckyhansmeyer.com/2015/04/09/autoshrinking-text-in-a-multiline-uilabel/
使用 Interface Builder 只需 3 个简单步骤即可解决问题:
希望对你有帮助!
【讨论】:
标记为解决方案的答案很老套且不精确。如果您正确设置以下属性,UILabel 会自动处理:
numberOfLines 必须为非零
adjustsFontSizeToFitWidth 必须是 YES
lineBreakMode 必须不为NSLineBreakByCharWrapping 或NSLineBreakByWordWrapping
【讨论】:
这是基于 itcedor 的 iOS 6 更新而更新到 iOS 7 的类别解决方案。
头文件:
#import <UIKit/UIKit.h>
@interface UILabel (MultiLineAutoSize)
- (void)adjustFontSizeToFit;
@end
以及实现文件:
@implementation UILabel (MultiLineAutoSize)
- (void)adjustFontSizeToFit {
UIFont *font = self.font;
CGSize size = self.frame.size;
for (CGFloat maxSize = self.font.pointSize; maxSize >= self.minimumScaleFactor * self.font.pointSize; maxSize -= 1.f)
{
font = [font fontWithSize:maxSize];
CGSize constraintSize = CGSizeMake(size.width, MAXFLOAT);
CGRect textRect = [self.text boundingRectWithSize:constraintSize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName:font}
context:nil];
CGSize labelSize = textRect.size;
if(labelSize.height <= size.height)
{
self.font = font;
[self setNeedsLayout];
break;
}
}
// set the font to the minimum size anyway
self.font = font;
[self setNeedsLayout]; }
@end
【讨论】:
NSString、-sizeWithFont:minFontSize:actualFontSize:forWidth:lineBreakMode: 上有一个方法,它显然自 iOS 2.0 以来就存在,但不幸的是,在没有建议的替代方案的情况下,在 iOS 7 中已弃用,因为不鼓励自动减小字体大小。我不太了解 Apple 对此的立场,因为他们在主题演讲等中使用了它,我认为如果字体大小在一个很小的范围内是可以的。这是使用此方法在 Swift 中的实现。
var newFontSize: CGFloat = 30
let font = UIFont.systemFontOfSize(newFontSize)
(self.label.text as NSString).sizeWithFont(font, minFontSize: 20, actualFontSize: &newFontSize, forWidth: self.label.frame.size.width, lineBreakMode: NSLineBreakMode.ByWordWrapping)
self.label.font = font.fontWithSize(newFontSize)
我不知道有什么方法可以在不使用已弃用的方法的情况下实现这一点。
【讨论】:
这些人找到了解决方案:
http://www.11pixel.com/blog/28/resize-multi-line-text-to-fit-uilabel-on-iphone/
他们的解决方案如下:
int maxDesiredFontSize = 28;
int minFontSize = 10;
CGFloat labelWidth = 260.0f;
CGFloat labelRequiredHeight = 180.0f;
//Create a string with the text we want to display.
self.ourText = @"This is your variable-length string. Assign it any way you want!";
/* This is where we define the ideal font that the Label wants to use.
Use the font you want to use and the largest font size you want to use. */
UIFont *font = [UIFont fontWithName:@"Marker Felt" size:maxDesiredFontSize];
int i;
/* Time to calculate the needed font size.
This for loop starts at the largest font size, and decreases by two point sizes (i=i-2)
Until it either hits a size that will fit or hits the minimum size we want to allow (i > 10) */
for(i = maxDesiredFontSize; i > minFontSize; i=i-2)
{
// Set the new font size.
font = [font fontWithSize:i];
// You can log the size you're trying: NSLog(@"Trying size: %u", i);
/* This step is important: We make a constraint box
using only the fixed WIDTH of the UILabel. The height will
be checked later. */
CGSize constraintSize = CGSizeMake(labelWidth, MAXFLOAT);
// This step checks how tall the label would be with the desired font.
CGSize labelSize = [self.ourText sizeWithFont:font constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];
/* Here is where you use the height requirement!
Set the value in the if statement to the height of your UILabel
If the label fits into your required height, it will break the loop
and use that font size. */
if(labelSize.height <= labelRequiredHeight)
break;
}
// You can see what size the function is using by outputting: NSLog(@"Best size is: %u", i);
// Set the UILabel's font to the newly adjusted font.
msg.font = font;
// Put the text into the UILabel outlet variable.
msg.text = self.ourText;
为了让它工作,必须在界面构建器中将 IBOutlet 分配给 UILabel。
"IBOutlet UILabel *msg;"
所有的优点都是 11 像素的人。
【讨论】:
itedcedor 的回答有一个 pwightman 指出的问题。此外,无需修剪空格。这是修改后的版本:
- (void)adjustFontSizeToFit {
UIFont *font = self.font;
CGSize size = self.frame.size;
for (CGFloat maxSize = self.font.pointSize; maxSize >= self.minimumScaleFactor * self.font.pointSize; maxSize -= 1.f) {
font = [font fontWithSize:maxSize];
CGSize constraintSize = CGSizeMake(size.width, MAXFLOAT);
CGSize labelSize = [self.text sizeWithFont:font constrainedToSize:constraintSize lineBreakMode:NSLineBreakByWordWrapping];
if(labelSize.height <= size.height) {
self.font = font;
[self setNeedsLayout];
break;
}
}
// set the font to the minimum size anyway
self.font = font;
[self setNeedsLayout];
}
【讨论】:
感谢 DaGaMs 提供此解决方案。
我已经更新如下:
1 - 使用 iOS 6(因为 minimumFontSize 和 UILineBreakModeWordWrap 都已弃用) 2 - 从标签的文本中去除空格,因为它会导致调整大小失败(你不想知道我花了多长时间才找到那个错误)
-(void)adjustFontSizeToFit
{
self.text = [self.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
UIFont *font = self.font;
CGSize size = self.frame.size;
for (CGFloat maxSize = self.font.pointSize; maxSize >= self.minimumScaleFactor; maxSize -= 1.f)
{
font = [font fontWithSize:maxSize];
CGSize constraintSize = CGSizeMake(size.width, MAXFLOAT);
CGSize labelSize = [self.text sizeWithFont:font constrainedToSize:constraintSize lineBreakMode:NSLineBreakByWordWrapping];
if(labelSize.height <= size.height)
{
self.font = font;
[self setNeedsLayout];
break;
}
}
// set the font to the minimum size anyway
self.font = font;
[self setNeedsLayout];
}
【讨论】:
minimumScaleFactor 是一些十进制值,例如 0.2。 maxSize >= self.minimumScaleFactor 然后会将大小减小到 0.2。相反,您不想要maxSize >= self.minimumScaleFactor * self.font.pointSize 或类似的东西吗?
我喜欢 DaGaMs 的回答,但是在使用像 UITableViewCells 这样可以返回 dequeueReusableCell: 的标签时,常规字体大小会继续缩小,即使对于一些文本较少的 tableView 单元格仍然需要原始字体大小并且可以利用原始标签的原始字体大小。
所以,我从 DaGaM 的类别开始作为起点,我创建了一个单独的类而不是一个单独的类别,并且我确保我的故事板中的 UILabel 使用这个新类:
#import "MultiLineAutoShrinkLabel.h"
@interface MultiLineAutoShrinkLabel ()
@property (readonly, nonatomic) UIFont* originalFont;
@end
@implementation MultiLineAutoShrinkLabel
@synthesize originalFont = _originalFont;
- (UIFont*)originalFont { return _originalFont ? _originalFont : (_originalFont = self.font); }
- (void)quoteAutoshrinkUnquote
{
UIFont* font = self.originalFont;
CGSize frameSize = self.frame.size;
CGFloat testFontSize = _originalFont.pointSize;
for (; testFontSize >= self.minimumFontSize; testFontSize -= 0.5)
{
CGSize constraintSize = CGSizeMake(frameSize.width, MAXFLOAT);
CGSize testFrameSize = [self.text sizeWithFont:(font = [font fontWithSize:testFontSize])
constrainedToSize:constraintSize
lineBreakMode:self.lineBreakMode];
// the ratio of testFontSize to original font-size sort of accounts for number of lines
if (testFrameSize.height <= frameSize.height * (testFontSize/_originalFont.pointSize))
break;
}
self.font = font;
[self setNeedsLayout];
}
@end
【讨论】:
- (void)quoteAutoshrinkUnquote; ......实际上,我已经开始使用类似但更复杂的东西来处理上的附件视图右侧以及它可以是 2 种不同尺寸的事实,允许每个单元或多或少的屏幕空间。