如何在iOS中优雅使用公式

12 阅读6分钟

规矩我懂:【 github.com/zwcshy/iOSK…

基于 KaTeX 的 iOS 公式渲染库,使用 Swift 实现,最低支持 iOS 12

本工程将 KaTeX 前端库的 JS、CSS、字体等资源打包进 App Bundle,通过 WKWebView 在本地离线渲染 LaTeX 公式,API 风格与前端 katex.render() 对齐。


目录


整体设计

架构思路

KaTeX 本身是 JavaScript 数学排版引擎,完整移植其 TypeScript 解析器到 Swift 成本极高。本库采用 Hybrid 方案

Swift 调用层(KatexView / KatexRenderer)
        ↓
WKWebView 加载本地 HTML 模板
        ↓
katex.min.js 解析 LaTeX 并排版
        ↓
katex.min.css + 字体文件 渲染视觉结果
        ↓
JavaScript 回传内容尺寸 → Swift 更新视图大小

数据流

sequenceDiagram
    participant App as Swift App
    participant View as KatexView
    participant Renderer as KatexRenderer
    participant WebView as WKWebView
    participant KaTeX as katex.min.js

    App->>View: setLatex("E=mc^2", options)
    View->>Renderer: render(latex, options, webView)
    Renderer->>WebView: evaluateJavaScript("renderMath(...)")
    WebView->>KaTeX: katex.render(latex, container, options)
    KaTeX-->>WebView: DOM + 尺寸
    WebView-->>Renderer: { width, height }
    Renderer-->>View: KatexRenderSize
    View->>View: 更新 width/height 约束

与 KaTeX 前端的关系

能力KaTeX 前端IOSKatex
数学公式✅ 原生支持✅ 一致
化学式文本 \ce{}✅ mhchem 扩展✅ 已集成 mhchem.min.js
苯环结构图❌ 不支持✅ 自定义 SVG + \includegraphics
离线渲染需自行打包资源✅ 内置 Bundle

工程结构

IOSKatex/
├── IOSKatex/                          # 主 App Target
│   ├── Library/                       # 📦 公式库核心(可复用)
│   │   ├── KatexView.swift            #   UIView 组件,界面展示公式
│   │   ├── KatexRenderer.swift        #   渲染引擎,WKWebView + JS 桥接
│   │   ├── KatexOptions.swift         #   渲染选项、化学模式枚举
│   │   ├── KatexBundle.swift          #   Bundle 资源路径
│   │   └── KatexError.swift           #   错误类型
│   ├── KaTeX/                         # 📁 KaTeX 静态资源(Copy Bundle Resources)
│   │   ├── katex.min.js               #   KaTeX 主引擎
│   │   ├── katex.min.css              #   样式(字体路径已修正)
│   │   ├── katex-template.html        #   WebView 加载的 HTML 模板
│   │   ├── mhchem.min.js              #   化学扩展(\ce{}、\pu{})
│   │   ├── auto-render.min.js         #   自动渲染扩展(可选)
│   │   ├── fonts/                     #   KaTeX 字体(60 个文件)
│   │   └── *.svg                      #   自定义苯环结构图(非 KaTeX 自带)
│   ├── ViewController.swift           # Demo 示例
│   ├── AppDelegate.swift              # iOS 12 兼容启动
│   └── SceneDelegate.swift            # iOS 13+ 场景管理
├── Scripts/
│   ├── copy-katex-assets.sh           # 从 npm / 本地 dist 复制 KaTeX 资源
│   └── embed-katex-bundle.sh          # 构建脚本参考(可选)
├── KaTeX-main/                        # KaTeX 前端源码(参考,不参与编译)
├── IOSKatex.xcodeproj/
├── IOSKatexTests/
└── README.md

模块职责

模块职责
KatexView面向 UI 的公式视图,自动管理 WebView 生命周期与尺寸
KatexRenderer单例 + 独立 WebView 渲染,提供 renderToString 等无 UI 能力
KatexOptions渲染参数,含化学模式选择
KatexBundle定位 Bundle 内 KaTeX 资源
katex-template.html注入 renderMath / remeasureMathSync 等 JS 函数

快速开始

环境要求

  • Xcode 15+
  • iOS 12.0+
  • Swift 5

运行 Demo

  1. 打开 IOSKatex.xcodeproj
  2. 选择模拟器或真机
  3. Run(⌘R

Demo 包含数学、物理、化学(mhchem 文本)、芳香烃(SVG 结构式)等示例。

最简示例

import UIKit

class MyViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let katexView = KatexView()
        katexView.setLatex("E = mc^2")
        katexView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(katexView)

        NSLayoutConstraint.activate([
            katexView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            katexView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
}

KatexView 会根据渲染结果自动设置 intrinsicContentSize,无需手动指定宽高。


核心 API

KatexView — 界面展示

let view = KatexView()

// 设置公式
view.setLatex("\\frac{a}{b}", options: KatexOptions(displayMode: true))

// 渲染完成回调
view.onRenderComplete = { result in
    switch result {
    case .success(let size):
        print("渲染尺寸: \(size.width) x \(size.height)")
    case .failure(let error):
        print("渲染失败: \(error.localizedDescription)")
    }
}

// 动态更新
view.latex = "\\sum_{i=1}^{n} i"
view.options.displayMode = true

KatexRenderer — 无 UI 渲染

// 渲染为 HTML 字符串
KatexRenderer.shared.renderToString("\\alpha + \\beta") { result in
    switch result {
    case .success(let html):
        print(html)  // 可用于 NSAttributedString / 富文本
    case .failure(let error):
        print(error)
    }
}

KatexOptions — 渲染配置

var options = KatexOptions()
options.displayMode = true       // 块级公式
options.throwOnError = false     // 出错时显示红色原文
options.macros = ["\\R": "\\mathbb{R}"]

化学公式两种模式

化学内容有两种渲染方式,通过 KatexChemistryMode 选择:

模式 1:mhchem 文本(与 KaTeX 前端一致)

使用 \ce{} 命令,渲染为带上下标、箭头、化学键的文字公式

katexView.setLatex(
    "\\ce{C6H6 + HNO3 -> C6H5-NO2 + H2O}",
    options: .chemistryText(displayMode: true)
)
属性
枚举.mhchemText
预设KatexOptions.chemistryText()
LaTeX\ce{H2O}\ce{2H2 + O2 -> 2H2O}
依赖mhchem.min.js(已内置)
苯环❌ 无结构图,仅文字

模式 2:SVG 结构式(iOS 扩展)

使用 \includegraphics 加载本地 SVG 苯环图,适合需要可视化结构的场景。

katexView.setLatex(
    "\\includegraphics[height=2em]{benzene.svg}\\quad \\mathrm{C_6H_6}",
    options: .chemistryStructure()
)
属性
枚举.structureSVG
预设KatexOptions.chemistryStructure()
LaTeX\includegraphics[height=2em]{aniline.svg}
依赖本地 SVG 文件 + trust: true(自动开启)
苯环✅ 六边形结构图

模式对比

mhchem 文本SVG 结构式
与前端一致❌(iOS 扩展)
苯环结构图
复杂反应式✅ 简洁需组合多个 SVG
离线资源仅 JSJS + SVG 文件
推荐场景一般化学式、反应方程式芳香烃结构、教学图示

手动指定模式

var options = KatexOptions()
options.chemistryMode = .mhchemText     // 或 .structureSVG / .none
options.displayMode = true
katexView.setLatex(latex, options: options)

渲染选项

KatexOptionsKaTeX Options 文档 对齐:

属性类型默认值说明
displayModeBoolfalse块级(true)/ 行内(false
chemistryModeKatexChemistryMode.none化学渲染模式
throwOnErrorBooltrue不支持命令时是否抛错
errorColorString"#cc0000"错误显示颜色
trustBoolfalse启用 \includegraphics(结构式模式自动开启)
macros[String: String][:]自定义宏
outputKatexOutput.htmlAndMathml输出格式

资源管理

更新 KaTeX 资源

KaTeX 核心资源来自 npm 包,通过脚本复制到工程:

# 从 npm 下载 KaTeX 0.17.0(推荐)
./Scripts/copy-katex-assets.sh

# 从本地 KaTeX-main/dist 复制(需先 build)
cd KaTeX-main && pnpm install && pnpm build
./Scripts/copy-katex-assets.sh --local

脚本会复制:

  • katex.min.js / katex.min.css
  • mhchem.min.js
  • fonts/ 目录下全部字体
  • 自动修正 CSS 中 fonts/ 路径(适配 Xcode 扁平化打包)

注意*.svg 苯环结构图为工程自定义资源,不会被脚本覆盖。

Bundle 打包说明

Xcode 使用 PBXFileSystemSynchronizedRootGroup 同步 IOSKatex/ 目录,KaTeX/ 下文件会被扁平化复制到 App Bundle 根目录。因此:

  • CSS 中字体路径已从 url(fonts/xxx) 修正为 url(xxx)
  • \includegraphics{benzene.svg} 直接使用 Bundle 根目录文件名
  • KatexBundle.resourceDirectoryURL 返回 Bundle.main.bundleURL

添加自定义 SVG 结构图

  1. 将 SVG 放入 IOSKatex/KaTeX/
  2. 确保 SVG 为合法 XML(下标用 <tspan baseline-shift="sub"> 而非 Unicode 下标字符)
  3. 在 LaTeX 中引用:
\includegraphics[height=2em, alt=描述]{your-structure.svg}
  1. 使用 .chemistryStructure()chemistryMode = .structureSVG

集成到其他工程

当前公式库代码位于 IOSKatex/Library/,资源位于 IOSKatex/KaTeX/。集成步骤:

  1. 复制文件

    • IOSKatex/Library/*.swift → 目标工程
    • IOSKatex/KaTeX/ 整个目录 → 目标工程
  2. 加入 Target

    • Swift 文件加入 Compile Sources
    • KaTeX/ 下所有资源加入 Copy Bundle Resources
  3. 依赖框架

    • UIKit
    • WebKit
  4. iOS 12 兼容

    • 若目标 App 使用 SceneDelegate,需确保 iOS 12 有 Storyboard / AppDelegate 回退路径
    • 避免在 Library 代码中使用 iOS 13+ 专属 API
  5. 使用

let formulaView = KatexView()
formulaView.setLatex("\\int_0^\\infty e^{-x^2} dx", options: KatexOptions(displayMode: true))

后续可进一步抽取为独立 Framework / Swift Package。


已知限制

  1. 渲染依赖 WKWebView:每个 KatexView 持有一个 WebView,大量公式同时展示时注意内存。
  2. 不支持 chemfig / tikz:完整 LaTeX 化学结构绘制不在 KaTeX 能力范围内,结构式需用 SVG 方案。
  3. 图片异步加载:含 \includegraphics 的公式会在渲染后多次重新测量尺寸(50ms / 150ms / 350ms)。
  4. 宽公式横向滚动:超长公式会撑开容器,外层需自行提供 UIScrollView
  5. trust 安全.structureSVG 模式开启 trust,仅加载可信任的本地 SVG,勿传入不可信 LaTeX。

常见问题

公式被截断 / 宽度不对

  • 确保外层容器未限制 KatexView 的 trailing 宽度
  • 含 SVG 的公式需等待图片加载完成后的二次测量
  • 参考 Demo 中 card.trailing = katexView.trailing + 12 的布局方式

SVG 显示裂图

  • 检查 SVG 是否为合法 XML(避免损坏的 Unicode 下标)
  • 确认 SVG 已加入 Copy Bundle Resources
  • 确认使用了 .chemistryStructure()trust: true

\ce{} 不生效

  • 确认 mhchem.min.js 在 Bundle 中
  • 确认 katex-template.html 已加载 <script src="mhchem.min.js">
  • 使用 .chemistryText() 预设

JSONSerialization 崩溃

  • LaTeX 字符串通过 [latex] 包装序列化,已在 KatexRenderer.jsonString(from:) 中处理,无需调用方关心

如何升级 KaTeX 版本

  1. 修改 Scripts/copy-katex-assets.sh 中的 KATEX_VERSION
  2. 修改 KatexBundle.version
  3. 运行 ./Scripts/copy-katex-assets.sh
  4. 重新编译验证 Demo

许可证