首页 在iOS中显示数学公式
文章
取消

在iOS中显示数学公式

尝试了两个组件,分别为 RichTextView, iosMath&YYText,使用起来都不复杂。他们的功能都不仅仅是显示数学公式,还有很多其他的功能,这里仅针对数学公式的支持进行比较。

图 0

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: ("\\(", "\\)"))
}
本文由作者按照 CC BY 4.0 进行授权

Flutter App 在 iOS 设备上启动时出现黑屏

从Xcode打开终端