#30 swift-markdown-ui

408 阅读3分钟

什么是 Markdown

Markdown 是一种轻量级标记语言,由 John Gruber 和 Aaron Swartz 在 2004 年创建,它允许人们使用易读易写的纯文本格式编写文档。

Markdown 语法简介

  • 标题:使用 # 来定义标题,# 的数量代表标题的级别
  • 强调:使用 *_ 来包含文本可以实现斜体、粗体等效果。例如 *斜体***粗体**
  • 列表:使用 *+- 来创建无序列表,使用数字后面跟 . 来创建有序列表
  • 链接和图片:使用 [描述](URL) 来创建链接,使用 ![描述][URL] 来插入图片
  • 引用:使用 > 来引用文本
  • 代码:使用一对反引号 ` 来标记行内代码,使用三对反引号 ``` 来标记代码块

Markdown 文件通常以 .md 或 .markdown 作为文件后缀名

swift-markdown-ui

swift-markdown-ui 是一个开源项目,用于在 SwiftUI 中显示和自定义 Markdown 文本,demo 如下:

struct CodeView: View {
  private let content = #"""
    You can call out code or a command within a sentence with single backticks.
    The text within the backticks will not be formatted.

    Use `git status` to list all new or modified files that haven't yet been committed.

    ```swift
    Group {
        Text("SwiftUI")
        Text("Combine")
        Text("Swift System")
    }
    .font(.headline)
    ```
    """#

  var body: some View {
    DemoView {
      Markdown(self.content)
    }
  }
}

数据 MarkdownContent

MarkdownContent 的数据结构是一个 BlockNode 类型的数组。

public struct MarkdownContent {
	let blocks: [BlockNode]
}

BlockNode 是一个枚举类型,它具体类型包括 blockquote, bulletedList, numberedList, taskList, codeBlock, htmlBlock, paragraph, heading, table, thematicBreak。

enum BlockNode: Hashable {
  case blockquote(children: [BlockNode])
  case bulletedList(isTight: Bool, items: [RawListItem])
  case numberedList(isTight: Bool, start: Int, items: [RawListItem])
  case taskList(isTight: Bool, items: [RawTaskListItem])
  case codeBlock(fenceInfo: String?, content: String)
  case htmlBlock(content: String)
  case paragraph(content: [InlineNode])
  case heading(level: Int, content: [InlineNode])
  case table(columnAlignments: [RawTableColumnAlignment], rows: [RawTableRow])
  case thematicBreak
}

一个块节点 BlockNode 可能包含若干子块节点 children BlockNode,也可能包含若干行内节点 InlineNode。InlineNode 也是一个枚举类型,它的具体类型包括 text, softBreak, lineBreak, code, html, emphasis, strong, strikethrough, link, image。

enum InlineNode: Hashable {
  case text(String)
  case softBreak
  case lineBreak
  case code(String)
  case html(String)
  case emphasis(children: [InlineNode])
  case strong(children: [InlineNode])
  case strikethrough(children: [InlineNode])
  case link(destination: String, children: [InlineNode])
  case image(source: String, children: [InlineNode])
}

解析器 MarkdownParser

MarkdownParser 使用 cmark-gfm 解析 markdown 字符串。

cmark-gfm 是一个开源的由 C 语言编写的 Markdown 快速解析器,它是 cmark 的一个扩展,用于支持 GitHub Flavored Markdown(GFM)。

GFM 是 Markdown 的一个扩展版本,GitHub 在其平台上的 README 文件使用了这种格式。MarkdownParser 将 cmark-gfm 解析的结果,转换成 BlockNode 数组。

具体流程如下:string -> cmark-gfm -> MarkdownParser -> [BlockNode]

视图 Markdown

每种类型的节点都有对应的 View 去渲染。

extension BlockNode: View {
  var body: some View {
    switch self {
    case .blockquote(let children):
      BlockquoteView(children: children)
    case .bulletedList(let isTight, let items):
      BulletedListView(isTight: isTight, items: items)
    case .numberedList(let isTight, let start, let items):
      NumberedListView(isTight: isTight, start: start, items: items)
    case .taskList(let isTight, let items):
      TaskListView(isTight: isTight, items: items)
    case .codeBlock(let fenceInfo, let content):
      CodeBlockView(fenceInfo: fenceInfo, content: content)
    case .htmlBlock(let content):
      ParagraphView(content: content)
    case .paragraph(let content):
      ParagraphView(content: content)
    case .heading(let level, let content):
      HeadingView(level: level, content: content)
    case .table(let columnAlignments, let rows):
      if #available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) {
        TableView(columnAlignments: columnAlignments, rows: rows)
      }
    case .thematicBreak:
      ThematicBreakView()
    }
  }
}

主题 Theme

支持三种不同的主题 Basic, DocC 和 GitHub,可通过调用 markdownTheme(_:) 切换主题。

参考链接