尝试了两个组件,分别为 RichTextView
, iosMath&YYText
,使用起来都不复杂。他们的功能都不仅仅是显示数学公式,还有很多其他的功能,这里仅针对数学公式的支持进行比较。
RichTextView
使用起来倒是不复杂,但当文本中含有比较高的数学公式时,发现渲染效果不太好,会有一段被截掉
1
2
| # in Podfile
pod 'RichTextView'
|
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
36
37
38
39
40
41
42
43
44
| // 使用#包裹字符串,标识使用 rawString,即不会使用 \() 进行变量替换
let mathText = #"已知\(y=f(x)+x^2\)是奇函数,且\(f(1)=1\)若\(g(x)=f(x)+2\),则\(g(-1)=\),然后接上一个空间高的公式:\(\lim_{x \to \infty} \frac{1}{n(n+1)}\)"#
func renderLatexWithRichTextView() {
// 构造组件
let richTextView = RichTextView(
input: "Test",
latexParser: LatexParser(),
font: font,
textColor: UIColor.black,
isSelectable: true,
isEditable: false,
latexTextBaselineOffset: 0,
interactiveTextColor: UIColor.blue,
textViewDelegate: nil,
frame: CGRect.zero,
completion: nil
)
// 添加布局约束
view.addSubview(richTextView)
richTextView.translatesAutoresizingMaskIntoConstraints = false
richTextView.snp.makeConstraints { make in
make.left.equalTo(16)
make.top.equalTo(88)
make.width.equalTo(200)
}
// 改造渲染数据
// 组件需求 Latex 使用 [math]x^n[/math] 包裹
// 实际后端给到的数据可能并非如此,需根据实际情况调整
// 例如,后端返回的数据中,公式部分使用 \(, \) 进行包裹,分别替换成 [math] 和 [/math]
let mathText = self.mathText
.replacingOccurrences(of: "\\(", with: "[math]")
.replacingOccurrences(of: "\\)", with: "[/math]")
// 跟新数据
richTextView.update(
input: mathText
)
// 调试:查看组件背景,以推断组件渲染大小是否符合预期
richTextView.backgroundColor = .systemRed
}
|
iosMath&YYText
1
2
3
| # in Podfile
pod 'YYText'
pod 'iosMath'
|
因为 YYLabel 的特性,可以控制显示行数,显示不全可以省略号显示,比较适合在列表中使用,并且相对于 RichTextView,布局计算更加准确,我倾向于这种方案。
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
| // 为 YYLabel 增加扩展方法
import iosMath
import YYText
extension YYLabel {
/**
用户为 YYLabel 增加数学表达式的功能
@param text: 含有数学公式的文本
@param pattern,标记数学公式的收尾识别符
@param fontSize: 字号
@param lineSpacing: 行高
*/
func setLatex(
_ text: String,
pattern: (begin: String, end: String)? = nil,
fontSize: CGFloat? = nil,
lineSpacing: CGFloat? = nil
) {
var text = text
// 防止传入的字符串含有待转义的字符,特此转换
if let pattern = pattern {
text = text
.replacingOccurrences(of: pattern.begin, with: "[math]")
.replacingOccurrences(of: pattern.end, with: "[/math]")
}
let attributedText = NSMutableAttributedString(string: text)
let font = (fontSize == nil ? self.font : UIFont.systemFont(ofSize: fontSize!)) ?? UIFont.systemFont(ofSize: 17)
attributedText.yy_font = font
attributedText.yy_lineSpacing = lineSpacing ?? 4
do {
// 正则匹配 [math]some text[/math]
let regex = try NSRegularExpression(pattern: "\\[math\\].*?\\[/math\\]")
let range = NSMakeRange(0, text.count)
let match = regex.matches(
in: text, range: range
).map {
(text as NSString).substring(with: $0.range)
}
if !match.isEmpty {
for item in match {
let range = (attributedText.string as NSString).range(of: item)
// 遍历匹配结果,并将匹配项移除前后的标记位
let fixedItem = item
.replacingOccurrences(of: "[math]", with: "")
.replacingOccurrences(of: "[/math]", with: "")
// 创建 mathLabel 作为 Attachment 插入到 YYLabel 中
func buildLatexLabel(_ text: String) -> UIView {
let latexLabel = MTMathUILabel(frame: .zero)
latexLabel.latex = text
latexLabel.fontSize = font.pointSize
latexLabel.sizeToFit()
return latexLabel
}
let latexView = buildLatexLabel(fixedItem)
attributedText.replaceCharacters(in: range, with: NSAttributedString.yy_attachmentString(
withContent: latexView,
contentMode: .center,
attachmentSize: latexView.frame.size,
alignTo: font,
alignment: .center
))
}
}
self.attributedText = attributedText
} catch {
print("render latex occurs error: \(error)")
}
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // 使用#包裹字符串,标识使用 rawString,即不会使用 \() 进行变量替换
let mathText = #"已知\(y=f(x)+x^2\)是奇函数,且\(f(1)=1\)若\(g(x)=f(x)+2\),则\(g(-1)=\),然后接上一个空间高的公式:\(\lim_{x \to \infty} \frac{1}{n(n+1)}\)"#
func renderLatexWithYYText() {
let label = YYLabel()
label.numberOfLines = 0
label.truncationToken = .init(string: "...")
label.preferredMaxLayoutWidth = 200
view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.snp.makeConstraints { make in
make.left.equalTo(16 + 200 + 8)
make.top.equalTo(88)
make.width.equalTo(200)
}
label.backgroundColor = .systemYellow
label.setLatex(mathText, pattern: ("\\(", "\\)"))
}
|