iOS 15 AttributedString API如何实现Markdown代码块渲染?
基于AttributedString实现Stack Overflow风格代码块的方案
原生AttributedString的Markdown解析器默认仅支持行内代码、标题、列表等基础元素,没有内置```包裹的多行代码块的样式映射,你可以按以下步骤实现需求:
- 第一步:预处理原始Markdown文本,通过正则匹配提取所有```包裹的代码块内容,记录代码块在全文的起止位置
- 第二步:调用AttributedString内置的Markdown解析方法处理非代码块部分的普通内容,再单独为提取到的代码块添加自定义属性:
- 字体:使用
UIFont.monospacedSystemFont(ofSize: 14, weight: .regular)等宽字体,保证代码字符对齐 - 背景:设置浅灰色背景(对应色值#f6f8fa,和Stack Overflow风格一致),上下预留4pt内边距、左右预留8pt内边距,添加4pt圆角
- 文字颜色:不需要代码高亮时用默认深黑色即可,需要高亮可额外对应配置关键字、注释的自定义颜色
- 字体:使用
- 第三步:配置代码块的段落样式,设置
headIndent和firstLineHeadIndent为8pt,让整段代码块整体缩进和正文区分,同时设置上下段间距为8pt,避免和前后正文衔接过密 - 第四步:如果要适配超长代码行,可以给展示文本的UILabel/UITextView添加水平滚动支持,和Stack Overflow的交互逻辑保持一致,也可直接设置自动换行适配容器宽度
你可以参考以下示例代码实现:
func generateAttributedText(from markdown: String) -> NSAttributedString { let fullAttributedString = NSMutableAttributedString() // 匹配多行代码块的正则 let codeBlockRegex = try! NSRegularExpression(pattern: "```([\\s\\S]*?)```", options: []) let matches = codeBlockRegex.matches(in: markdown, range: NSRange(markdown.startIndex..., in: markdown)) var lastEndIndex = markdown.startIndex for match in matches { // 先拼接代码块之前的普通文本 let plainTextRange = lastEndIndex..<markdown.index(markdown.startIndex, offsetBy: match.range.lowerBound) if !plainTextRange.isEmpty { let plainStr = String(markdown[plainTextRange]) if let attributedPlain = try? AttributedString(markdown: plainStr) { fullAttributedString.append(NSAttributedString(attributedPlain)) } } // 处理代码块内容并添加样式 let codeContentRange = Range(match.range(at: 1), in: markdown)! let codeStr = String(markdown[codeContentRange]) let attributedCode = NSMutableAttributedString(string: codeStr) // 等宽字体配置 let font = UIFont.monospacedSystemFont(ofSize: 14, weight: .regular) attributedCode.addAttribute(.font, value: font, range: NSRange(location: 0, length: codeStr.count)) // 背景色配置 attributedCode.addAttribute(.backgroundColor, value: UIColor(red: 0.96, green: 0.97, blue: 0.98, alpha: 1), range: NSRange(location: 0, length: codeStr.count)) // 段落样式配置 let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.headIndent = 8 paragraphStyle.firstLineHeadIndent = 8 paragraphStyle.lineSpacing = 3 paragraphStyle.paragraphSpacing = 8 attributedCode.addAttribute(.paragraphStyle, value: paragraphStyle, range: NSRange(location: 0, length: codeStr.count)) fullAttributedString.append(attributedCode) lastEndIndex = markdown.index(markdown.startIndex, offsetBy: match.range.upperBound) } // 拼接最后剩余的普通文本 if lastEndIndex < markdown.endIndex { let plainStr = String(markdown[lastEndIndex...]) if let attributedPlain = try? AttributedString(markdown: plainStr) { fullAttributedString.append(NSAttributedString(attributedPlain)) } } return fullAttributedString }
如果需要代码高亮效果,只需在上述代码的基础上,对提取到的代码内容按对应编程语言的关键字规则做二次属性添加即可,无需引入额外第三方库。
内容的提问来源于stack exchange,提问作者Todd Mathison
相关产品推荐
相关产品推荐

