如何用SwiftUI实现类似备忘录的富文本编辑器?
SwiftUI 富文本编辑器实现方案
一、SwiftUI 标准TextEditor的局限
标准的TextEditor仅支持纯文本编辑,你提到的文本样式(标题、副标题等)、格式(粗体/斜体等)、颜色、列表、对齐、块引用这些富文本功能,SwiftUI目前没有内置支持,不存在你忽略的现有API。
二、实现富文本编辑器的可行方案
你需要基于现有组件构建或借助成熟方案,以下是两种主流思路:
1. 包装UIKit的UITextView(推荐)
UITextView原生支持NSAttributedString,可以轻松实现所有你需要的富文本功能,通过SwiftUI的UIViewRepresentable可以将其包装成SwiftUI可用的视图。核心步骤:
- 创建
UIViewRepresentable结构体,将UITextView嵌入SwiftUI视图层级 - 用
NSAttributedString替代原有的String存储文本内容 - 实现格式切换逻辑:通过操作选中范围的属性(如字体、颜色、段落样式)来应用粗体、斜体、列表等效果
- 处理文本编辑回调,同步富文本数据到你的SwiftData模型(可将
NSAttributedString转成Data存储)
示例代码思路(替换原TextEditor):
import SwiftUI import UIKit import SwiftData struct RichTextEditor: UIViewRepresentable { @Binding var attributedText: NSAttributedString var onTextChange: (NSAttributedString) -> Void func makeUIView(context: Context) -> UITextView { let textView = UITextView() textView.delegate = context.coordinator textView.font = UIFont.systemFont(ofSize: 16) return textView } func updateUIView(_ uiView: UITextView, context: Context) { if uiView.attributedText != attributedText { uiView.attributedText = attributedText } } func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, UITextViewDelegate { var parent: RichTextEditor init(_ parent: RichTextEditor) { self.parent = parent } func textViewDidChange(_ textView: UITextView) { parent.onTextChange(textView.attributedText) } } } // 在NoteView中使用 struct NoteView: View { let note: Note @Environment(\.modelContext) private var context @State private var attributedText: NSAttributedString = NSAttributedString(string: "") var body: some View { VStack { // 格式工具栏示例:添加粗体、项目符号等按钮 HStack { Button("加粗") { applyBold() } Button("项目符号") { applyBulletList() } // 可扩展其他格式按钮:斜体、下划线、对齐等 } .padding(.horizontal) RichTextEditor(attributedText: $attributedText) { newText in attributedText = newText // 同步到SwiftData:将NSAttributedString转成Data存储 if let data = try? NSKeyedArchiver.archivedData(withRootObject: newText, requiringSecureCoding: false) { note.bodyData = data } } .padding() } .onAppear { // 从SwiftData加载富文本数据 if let data = note.bodyData, let text = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSAttributedString.self, from: data) { attributedText = text } else { attributedText = NSAttributedString(string: note.body ?? "") } } } // 示例:实现加粗切换功能 private func applyBold() { guard let textView = UIApplication.shared.sendAction(#selector(getter: UITextView.self), to: nil, from: nil, for: nil) as? UITextView, let selectedRange = textView.selectedTextRange else { return } let range = textView.selectedRange var attributes = textView.attributedText.attributes(at: range.location, effectiveRange: nil) if let font = attributes[.font] as? UIFont { let newFontDescriptor = font.fontDescriptor.symbolicTraits.contains(.traitBold) ? font.fontDescriptor.withSymbolicTraits([]) : font.fontDescriptor.withSymbolicTraits(.traitBold) attributes[.font] = UIFont(descriptor: newFontDescriptor ?? font.fontDescriptor, size: font.pointSize) textView.textStorage.addAttributes(attributes, range: range) } } // 示例:实现项目符号列表 private func applyBulletList() { guard let textView = UIApplication.shared.sendAction(#selector(getter: UITextView.self), to: nil, from: nil, for: nil) as? UITextView, let selectedRange = textView.selectedTextRange else { return } let range = textView.selectedRange let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.headIndent = 15 paragraphStyle.firstLineHeadIndent = -15 paragraphStyle.minimumLineHeight = 20 paragraphStyle.listStyle = .disc textView.textStorage.addAttribute(.paragraphStyle, value: paragraphStyle, range: range) } } // 更新Note模型以支持富文本存储 @Model class Note { var body: String? var bodyData: Data? // 存储富文本的二进制数据 init(body: String? = nil) { self.body = body } }
2. 使用第三方富文本库
有一些专为SwiftUI打造的富文本编辑器库,已封装好UITextView的交互和格式逻辑,可直接集成使用,比如:
- RichTextEditor:支持基础格式、列表、对齐等功能,API贴合SwiftUI风格
- AttributedTextEditor:基于NSAttributedString构建,提供灵活的样式自定义能力
这类库能帮你省去大量底层封装工作,直接专注于业务逻辑。
三、参考资源
- 苹果官方文档:
NSAttributedString属性设置、UITextView代理方法、UIViewRepresentable使用指南 - 苹果WWDC教程:UIKit与SwiftUI交互、富文本编辑最佳实践相关内容
内容的提问来源于stack exchange,提问作者milkycards




