iOS换行更均匀?

dldeef67  于 2023-06-25  发布在  iOS
关注(0)|答案(5)|浏览(121)

我有一个标题,我放进了一个UILabel。我可以把文本 Package 成两行,但通常第二行只有一个小字。有没有一种方法可以告诉系统把线条包得更均匀一点?
标题是动态获取的,所以我不相信我可以提前确定。

yptwkmov

yptwkmov1#

我继续创建了一个名为EvenlyWrappedLabel的CocoaPod,它的灵感来自Erik van der Neut的答案。它有几个简洁的特点:
1.防止单个单词孤儿占据整个底线。
示例:

This sentence has a single
orphan.

变成:

This sentence has
a single orphan.

1.防止文本在顶部分组,感觉头重脚轻。
示例:

This sentence has a lot of words on
the top line.

变成:

This sentence has a lot
of words on the top line.

1.选项可在每一行中均匀分布文本。
示例:

This only takes up one line.

成为 *:

This only
takes up
one line.
    • numberOfLines = 3useEveryLine = true *

1.使用自动布局约束并遵守固有大小。
1.非常易于集成,只需将let label = UILabel()替换为let label = EvenlyWrappedLabel()即可。
1.一个糟糕的屁股应用程序。

如果你不使用CocoaPods,你可以在GitHub上找到源代码。好好享受吧!

x6h2sr28

x6h2sr282#

您可以计算出单个行的长度,然后取该宽度,除以2,并将结果用作2行标签的宽度约束。这可能会给你一个更好的视觉效果,但如果你有长单词,会导致你的标签是3行(除非超过2行是可以的),你可能需要稍微调整一下。
这种方法的缺点是需要做一些额外的工作,但它可能会起到作用。例如:

NSString *text = @"This is my very long title I want evenly on 2 lines";
    UILabel *theLabel = [[UILabel alloc] init];
    theLabel.text = text;
    // ...other label setup here, like font, etc
    [theLabel sizeToFit];

    CGSize constraint = CGSizeMake(theLabel.frame.size.width / 2.0, 1000);
    CGSize labelSize = [theLabel.text sizeWithFont:theLabel.font constrainedToSize:constraint lineBreakMode:UILineBreakModeWordWrap];

    theLabel.numberOfLines = 0;
    theLabel.lineBreakMode = UILineBreakModeWordWrap;
    CGRect frame = theLabel.frame;
    frame.size = labelSize;
    theLabel.frame = frame;

应该可以了警告:根据记忆写的。:—)

kkbh8khc

kkbh8khc3#

我不相信有一个简单的方法可以做到这一点。NSString的UIKit附加功能允许您确定显示具有特定字体的字符串所需的尺寸。您可以使用所有可用宽度来计算所需的高度,然后在循环中运行相同的计算,减小宽度,直到需要额外的垂直空间。然后使用前一个值作为标签的宽度,并将其适当对齐。

jdg4fx2g

jdg4fx2g4#

对于一个多语言的应用程序,我需要这个完全相同的问题,以及修复。该应用程序可以以9种不同的语言显示其UI,并且不同语言之间的消息长度差异很大。正因为如此,我最终对一些多行消息采用了一些不幸的默认单词 Package 。例如,有几个例子,日语翻译结束时,一整行文本后面只有一个或两个字符在下一行。
我在Layout Utility类中添加了以下两个方法,为我解决了这个问题(参见下面的代码)。此解决方案适用于具有任意行数(1、2、3、更多...)和任意语言的标签。(将ECLayoutUtility更改为您自己的类名。)
基本思想是这样的:
1.给定一个显示文本的UILLabel,检测其当前高度。
1.对标签的最小宽度进行二分搜索,以 * 不 * 增加高度。
在下面的代码中,您可以通过granularity变量的while循环来控制精度和圈数之间的权衡。
此修复之前有问题的日文标签示例:

修复后的相同日文标签:

这是我添加到Layout Utility类中的两个方法。我这样将它们分开,所以你也可以查询新的大小,以防你正在使用Autolayout,并希望使用大小信息来操纵约束,而不是直接调整标签本身的大小:

+ (void)wordWrapEvenlyTextInUILabel:(UILabel *)uiLabel
                        forMaxWidth:(CGFloat)maxLabelWidth
{
    [ECLayoutUtility resizeView:uiLabel
                         toSize:[self getLabelSizeWithMinimumWidthForLabel:uiLabel
                                                               forMaxWidth:maxLabelWidth]];
}

+ (CGSize)getLabelSizeWithMinimumWidthForLabel:(UILabel *)uiLabel
                                   forMaxWidth:(CGFloat)maxLabelWidth
{
    NSLog(@"Current size for label \"%@\" is %@", uiLabel.text, NSStringFromCGSize(uiLabel.frame.size));

    // Start off by determining what height the label would get for the given maximum width:
    CGSize labelSize = [uiLabel.text boundingRectWithSize:CGSizeMake(maxLabelWidth, MAXFLOAT)
                                                  options:NSStringDrawingUsesLineFragmentOrigin
                                               attributes:@{ NSFontAttributeName:uiLabel.font }
                                                  context:nil].size;

    // Constraining the label to that height, now figure out the mimumum width for
    // which this label still shows its text within that given height. We find
    // that height with a binary search for the smallest label width that does
    // not increment the label height.

    CGFloat maxWidth    = maxLabelWidth;
    CGFloat testWidth   = 0.5 * maxWidth;
    CGFloat minWidth    = 0;
    CGFloat granularity = 1;

    while (maxWidth > testWidth + granularity)
    {
        CGFloat newHeight = [uiLabel.text boundingRectWithSize:CGSizeMake(testWidth, MAXFLOAT)
                                                       options:NSStringDrawingUsesLineFragmentOrigin
                                                    attributes:@{ NSFontAttributeName:uiLabel.font }
                                                       context:nil].size.height;

        NSLog(@"H: %.2f, min W: %.2f, mid W: %.2f, max W: %.2f", newHeight, minWidth, testWidth, maxWidth);

        if (newHeight > labelSize.height)
        {
            // Width reduction lead to height increase, so new width is too small.
            // Now set the new width to mid-point between what we just tried and
            // the last known acceptable width:
            minWidth   = testWidth;                         // increase the lower bound
            testWidth += 0.5 * (maxWidth - testWidth);      // increase width for next test
        }
        else
        {
            // Height not affected by the width reduction, so lets try to reduce it even more:
            maxWidth   = testWidth;                         // reduce the upper bound
            testWidth -= 0.5 * (testWidth - minWidth);      // reduce width for next test
        }
    }

    CGSize newSize = CGSizeMake(maxWidth, labelSize.height);
    NSLog(@"New size for label \"%@\" is %@", uiLabel.text, NSStringFromCGSize(newSize));

    return newSize;
}

我在View Controller中从-viewWillLayoutSubviews调用+wordWrapEvenlyTextInUILabel:forMaxWidth:方法,如下所示:

// Make sure that any text in the promo message that is wrapped over multiple lines
// is wrapped evenly, so we don't get very uneven lines that look ugly:
[ECLayoutUtility wordWrapEvenlyTextInUILabel:_promoMessageLabel
                                 forMaxWidth:_promoMessageLabel.frame.size.width];

我已经测试了这个不同的标签在不同的语言,它看起来是做的工作完美。这里有一个越南的例子:

我希望这对其他人也有帮助:-)
谢谢
艾瑞克

pbpqsu0x

pbpqsu0x5#

从这个开始:

对此:

**杀死孤儿的设计点:**从技术上讲,该函数不会杀死孤儿(自由浮动的单词,如顶部图片中的“Soul”和“life”),但会调整每行单词以尽可能接近假设的平均长度。它假定您已经创建了一个包含文本的标签。

代码如下:

-(NSString *)killOrphans:(UILabel *)label {

int numberOfLines = [self numberOfLines:label];
UIFont * font = label.font;

float total = 0.0f;
NSMutableArray * widths = [NSMutableArray new];
NSArray * splitWords = [label.text componentsSeparatedByString:@" "];
for (int n = 0; n < (int)splitWords.count; n++){
    NSString * word = splitWords[n];
    NSString * spaceWord = [NSString stringWithFormat:@"%@ ", word];
    float wordWidth = [spaceWord sizeWithAttributes:@{NSFontAttributeName:font}].width;
    [widths addObject:@(wordWidth)];
    total += wordWidth;
}

total -= 1.0f; //remove last space.
/* might be easier to draw out long UILabel and grab width directly */

float meanLengthPerLine = total / (float)numberOfLines;
NSString * string;
int newLineCount = 0;
meanLengthPerLine -= 1.0f;

do {
    
    newLineCount = 1;
    string = @"";
    meanLengthPerLine += 1.0f;
 
    float runWidth = 0.0f;
    for (int n = 0; n < (int)splitWords.count; n++){
        
        NSString * word = splitWords[n];
        float width = [widths[n] floatValue];
        float potential = runWidth + width;
        if (potential <= meanLengthPerLine){
            string = [NSString stringWithFormat:@"%@ %@", string, word];
            runWidth += width;
        } else {
            newLineCount++;
            
            string = [NSString stringWithFormat:@"%@\n%@", string, word];
            runWidth = width;
        }
    }
    
} while (newLineCount > numberOfLines);

/* can set uilabel.text directly here or return */
return [string substringFromIndex:1]; //Remove first space

}

当每行的平均宽度var meanLengthPerLine增加时,它会在前一行中创建空间,以允许更多的单词“折叠”。如果循环被绘制出来,它将从一个类似于瘦5线标签的东西变成均匀分布的三条线。以下是正在发生的事情的一个高层次:
1.计算标签文本的总宽度,就好像它是在长UILabel上拼写出来的。
1.计算平均值(除以行数-分离函数)
1.使用平均值作为起点-这是每行的目标宽度。
1.将单词添加到行中,直到超过“均值”阈值。
1.如果它违反,则创建一个新行。
1.如果在添加所有字之后,有更多的行比UILabel应该有(在上面的例子中,标签需要三行),然后增加平均宽度一位并重复这个过程。这为以前被推下的单词(因为违规)现在占据了一排的位置。在上面的代码中,长度增加了一个像素meanLengthPerLine += 1.0f;,但您可以保存一些循环并添加2或3个像素,并根据您的意愿更快地获得答案,但不太准确。
1.最终,您将找到所需行数的“最紧”平均宽度。

精确计算行数这里是精确计算行数的代码,因为许多现有的解决方案基于行高或其他方法包括通过舍入/单元格的一些不准确性。这个代码更长,更烦人,但似乎成立:

-(int)numberOfLines:(UILabel *)label{

NSArray * splitWords = [label.text componentsSeparatedByString:@" "];

int lineCount = 1;
float currentLineWidth = 0.0f;

for (int n = 0; n < (int)splitWords.count; n++){
    
    NSString * word = splitWords[n];
    NSString * spaceWord = [NSString stringWithFormat:@"%@ ", word];
    float wordWidth = [spaceWord sizeWithAttributes:@{NSFontAttributeName:label.font}].width;
    float potentialWidth = currentLineWidth + wordWidth;
    
    if (potentialWidth <= label.frame.size.width){
        
        currentLineWidth = potentialWidth;
        
    } else {
        
        wordWidth = [word sizeWithAttributes:@{NSFontAttributeName:label.font}].width; //try without the space
        potentialWidth = currentLineWidth + wordWidth;
        
        if (potentialWidth <= label.frame.size.width){
            
            currentLineWidth = potentialWidth;
            
        } else {
            
            lineCount++;
            currentLineWidth = wordWidth;
        }
    }
}

return lineCount;
}

相关问题