UILabel超过指定行数时显示更多

在开发中经常会碰到只显示少量介绍文字,点击更多的时候才会查看更多全部内容,如何在不使用第三方库的情况下准确地在行末加上“更多”的文字呢?

我们可以为 UILabel 添加扩展方法来实现。

1. 获取每一行的文字数组

首先,获取每一行的文本,目的有两个,第一是判断文本行数是否超过了指定的行数,第二是为了获取最后一行文本以拼接“更多”的字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/// 获取 Label 每行内容 得到一个数组
private func getLines(fitWidth: CGFloat) -> [String] {
guard let text = text, !text.isEmpty else {
return []
}

let attrString = NSMutableAttributedString(string: text)

attrString.addAttributes(
attributes,
range: NSMakeRange(0, attrString.length))

let frameSetter = CTFramesetterCreateWithAttributedString(attrString)
let textRect = CGRect(x: 0, y: 0, width: fitWidth, height: 10000)
let textRectPath = UIBezierPath(rect: textRect).cgPath
let frame = CTFramesetterCreateFrame(
frameSetter, CFRangeMake(0, 0),
textRectPath,
nil)
let lines = CTFrameGetLines(frame) as! [CTLine]
var linesArray = [String]()


for line in lines {
let lineRange = CTLineGetStringRange(line)
let range = NSMakeRange(
lineRange.location,
lineRange.length)
let lineString = NSString(string: text).substring(with: range)
linesArray.append(lineString)
}
return linesArray
}

2. 对文本的最后一行进行拼接

首先把省略提示文字拼接到末尾,计算长度,如果长度超过可显示的宽度,则截取最后一个字。如此循环,直到文字宽度小于指定宽度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private func trimmingTail(for string: String, appendString: String, fitWidth: CGFloat) -> String {

var trimmedString = string
var stringWidth = calculateLength(trimmedString + appendString)
while stringWidth > fitWidth {
trimmedString.removeLast()
stringWidth = calculateLength(trimmedString + appendString)
}
return trimmedString
}

private func calculateLength(_ string: String) -> CGFloat {
let targetString = NSString(string: string)
let size = targetString.boundingRect(
with: CGSize(width: 1000, height: 250),
options: [.usesLineFragmentOrigin],
attributes: attributes,
context: nil)
return size.width
}

3. 根据规则设置

最后则是判断是否应该添加更多的选项。如果没有超过指定行数,则不进行任何处理,如果超过了指定行数,则为最后一行文本添加“更多”文字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
func setReadMore(_ appendString: String, fitWidth: CGFloat) {
let lineTexts = getLines(fitWidth: fitWidth)
if lineTexts.count <= numberOfLines {
isUserInteractionEnabled = false
attributedText = NSAttributedString(string: text ?? "", attributes: attributes)
return
}

var contentText = String()

for i in 0..<numberOfLines {
var lineText = lineTexts[i]
if i == numberOfLines - 1 {
lineText = trimmingTail(for: lineText, appendString: appendString, fitWidth: fitWidth)
.trimmingCharacters(in: .newlines)
}
contentText.append(lineText)
}
let result = contentText + appendString

let resultAttrString = NSMutableAttributedString(string: result)

let range = NSMakeRange(0, resultAttrString.length)
resultAttrString.addAttributes(
attributes,
range: range)

let linkRange = NSMakeRange(resultAttrString.length - appendString.count, appendString.count)
resultAttrString.addAttributes(
[
.foregroundColor: UIColor.blue,
],
range: linkRange)
attributedText = resultAttrString
}

这样基本的功能就已经实现了,只需要在填充文本之后调用 setReadMore 就可以实现显示更多的样式了。使用方式如下:

1
2
3
label.numberOfLines = 5
label.text = "Some text content..."
label.setReadMore("显示全文", fitWidth: 300)

如果你发现本文有任何疏漏错误,还请不吝指出,多谢。