如何在Swift中限制String或attributed String的字符数量

11
给定一个具有x个字符的(html)字符串。该字符串将被格式化为属性字符串,然后显示在UILabel中。 UILabel的高度为>= 25<= 50,以限制行数为2。
由于字符串中有在格式化的属性字符串中不可见的字符,例如<b> / <i>,因此最好的方法是限制属性字符串的字符数。 UILabel属性.lineBreakMode = .byTruncatingTail会导致单词被截断。

enter image description here

目标是在UILabel的空间限制超过字符数时,在单词之间进行截断。期望的maxCharacterCount = 50。确定maxCharacterCount之前的最后一个空格。将字符串截断,并将...附加为UILabel的最后字符。

限制字符数的最佳方法是什么?非常感谢您的帮助。


你自己尝试过吗?或者我没有正确理解问题。如果你限制标签的大小,字符串将自动被截断 - LinusGeffarth
通过在标签上设置lineBreakMode属性,让UILabel来处理它。请参阅UILabel文档中的“自定义标签外观”部分。 - Code Different
@LinusG。我确实知道,但是关键是,例如字符串有200个字符,要在47处截断它并附加3个“。”,但也许lineBreakMode可以做到...现在正在检查它。 - David Seek
但如果标签自动添加了三个点,为什么还需要手动添加它们呢?或者你需要这些点不仅在视觉上成为字符串的一部分,而且实际上也是它的一部分吗? - LinusGeffarth
请查看我的编辑。一开始,我不知道有自动操作的可能性,但是剪切的单词并不完全符合我的需求。我希望在单词之间进行剪切,因此在所需字符数前的最后一个“空格”用3个 ...替换。 - David Seek
在你的问题中,你描述了一个简单的算法来实现你想要的效果。为什么不使用它呢?只需循环遍历字符串,并在小于你的 maxCharacterCount 的空格处截断即可。 - nathangitter
4个回答

12

从完整的字符串和已知标签的两行高度及其已知宽度开始,不断从字符串末尾截断单词,直到该宽度下,字符串的高度小于标签的高度。然后再截取一个单词,并添加省略号,将结果字符串放入标签中。

通过这种方式,我得到了以下结果:

enter image description here

请注意,“time”后面的单词没有开始;我们在插入省略号时停在一个精确的单词结尾。下面是我的做法:

    lab.numberOfLines = 2
    let s = "Little poltergeists make up the principle form of material " +
        "manifestation. Now is the time for all good men to come to the " +
        "aid of the country."
    let atts = [NSFontAttributeName: UIFont(name: "Georgia", size: 18)!]
    let arr = s.components(separatedBy: " ")
    for max in (1..<arr.count).reversed() {
        let s = arr[0..<max].joined(separator: " ")
        let attrib = NSMutableAttributedString(string: s, attributes: atts)
        let height = attrib.boundingRect(with: CGSize(width:lab.bounds.width, 
                                                      height:10000),
                                         options: [.usesLineFragmentOrigin],
                                         context: nil).height
        if height < lab.bounds.height {
            let s = arr[0..<max-1].joined(separator: " ") + "…"
            let attrib = NSMutableAttributedString(string: s, attributes: atts)
            lab.attributedText = attrib
            break
        }
    }

当然,可以更加复杂地确定什么构成了“单词”以及测量条件,但上述内容演示了这种类型的一般通用技术,并应足以帮助你入门。


这非常惊人。非常感谢你的努力。 - David Seek
这样做会丢失有关原始字符串的信息。只要UILabel不改变,外观就可以。例如,如果您旋转设备,则三个点将出现在错误的位置。 - Giuseppe Lanza
@GiuseppeLanza 我完全同意,如果标签的范围发生变化,您将不得不再次执行此操作。基本上,OP要求手动执行布局,而不是标签和Text Kit已经执行的操作。 —如果保留原始字符串,则不会丢失原始字符串。我的代码不是“最终答案”,它只演示了一种简单的技术,用于按照OP描述的方式更改字符串。它甚至不是正确的答案;真正的解决方案是使用一个不可编辑的UITextView,具有自定义的Text Kit堆栈,并在那里执行布局。 - matt
但是你的回答回答了我的问题并解决了我的问题,因此你已经回答了它,即使你不会称其为“正确答案” :-) - David Seek

5
试试这个:
import UIKit

class ViewController: UIViewController {
var str = "Hello, playground"
var thisInt = 10
@IBOutlet weak var lbl: UILabel!
var lblWidth : CGFloat = 0.0

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    lblWidth = lbl.bounds.width

    while str.characters.count <= thisInt - 3 {
        str.remove(at: str.index(before: str.endIndex))
        str.append("...")
    }
    lbl.text = str
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}


}

string width calculation link


1
这是一个简短而快速的想法,但由于String具有像<b>这样的元素,标签的最终内容可能比期望的想法要短得多。 - David Seek

2

仅供参考,Matt的回答是正确的,但您可以通过创建自己的UILabel子类来消除任何可能出现的问题,该子类将在变量中存储原始值。然后,当方向发生更改时,您可以从视图控制器更新其布局。


我现在并不真正理解你的方法的优势。你能否更具体地说明一下你认为的“可能问题”是什么?以及你将存储哪些值? - David Seek
看,你将会有一个原始值,它将被存储在变量中,例如“玛丽有一只小羊”。另一个部分是设置将要被截取的文本,例如“玛丽有...”。可能出现的问题是指,当你使用默认的UILabel时,你无法检索到原始文本,而使用自定义标签则可以,因为你已经存储了原始值。这就是我想说的全部内容。你不需要将其存储在VC或其他任何地方,但是你的自定义标签将为你保存它。因此,在旋转或调整大小时不会出现任何问题,因为你始终可以重新计算。 - Luzo

0

Mikael Weiss的回答进行快速更正。

if str.count > thisInt +3{
    while str.count >= thisInt {
        str.remove(at: str.index(before: str.endIndex))
    }
    str.append("...")
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接