如何在 SwiftUI 文本中显示 HTML 或 Markdown?

How to show HTML or Markdown in a SwiftUI Text?

如何设置 SwiftUI Text 以显示呈现的 HTML 或 Markdown?

像这样:

Text(HtmlRenderedString(fromString: "<b>Hi!</b>"))

或MD:

Text(MarkdownRenderedString(fromString: "**Bold**"))

也许我需要不同的视图?

Text 只能显示 Strings。 您可以将 UIViewRepresentableUILabelattributedText.

一起使用

可能稍后 SwiftUI.Text 会支持 attributedText 文本。

您可以尝试使用包 https://github.com/iwasrobbed/Down,从您的 markdown 字符串生成 HTML 或 MD,然后创建自定义 UILabel 子类并使其可用于 SwiftUI,如下例所示:

struct TextWithAttributedString: UIViewRepresentable {

    var attributedString: NSAttributedString

    func makeUIView(context: Context) -> ViewWithLabel {
        let view = ViewWithLabel(frame: .zero)
        return view
    }

    func updateUIView(_ uiView: ViewWithLabel, context: Context) {
        uiView.setString(attributedString)
    }
}

class ViewWithLabel : UIView {
    private var label = UILabel()

    override init(frame: CGRect) {
        super.init(frame:frame)
        self.addSubview(label)
        label.numberOfLines = 0
        label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func setString(_ attributedString:NSAttributedString) {
        self.label.attributedText = attributedString
    }

    override var intrinsicContentSize: CGSize {
        label.sizeThatFits(CGSize(width: UIScreen.main.bounds.width - 50, height: 9999))
    }
}

我在这方面取得了一定的成功,但无法正确设置标签子类的框架。也许我需要为此使用 GeometryReader。

我专门为 SwiftUI 创建了一个 markdown 库:

https://github.com/Lambdo-Labs/MDText

随时投稿!

如果您不需要专门使用文本视图。您可以创建一个显示 WKWebView 和简单调用 loadHTMLString() 的 UIViewRepresentable。

import WebKit
import SwiftUI

struct HTMLStringView: UIViewRepresentable {
    let htmlContent: String

    func makeUIView(context: Context) -> WKWebView {
        return WKWebView()
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {
        uiView.loadHTMLString(htmlContent, baseURL: nil)
    }
}

在你的 body 中简单地调用这个 object 像这样:

import SwiftUI

struct Test: View {
    var body: some View {
        VStack {
            Text("Testing HTML Content")
            Spacer()
            HTMLStringView(htmlContent: "<h1>This is HTML String</h1>")
            Spacer()
        }
    }
}

struct Test_Previews: PreviewProvider {
    static var previews: some View {
        Test()
    }
}

既然我找到了另一个解决方案,我想与您分享。

创建一个新的视图代表

struct HTMLText: UIViewRepresentable {

   let html: String
    
   func makeUIView(context: UIViewRepresentableContext<Self>) -> UILabel {
        let label = UILabel()
        DispatchQueue.main.async {
            let data = Data(self.html.utf8)
            if let attributedString = try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) {
                label.attributedText = attributedString
            }
        }

        return label
    }
    
    func updateUIView(_ uiView: UILabel, context: Context) {}
}

以后像这样使用它:

HTMLText(html: "<h1>Your html string</h1>")

就在 swiftUI 中呈现 HTML 而言,有许多解决方案,但是为了通过 AttributedText 将其呈现为通用 UILabel,这是我在结合我发现的其他一些解决方案后采用的方法。

这是您将从父 swiftUI 视图中使用的 UIViewRepresentable:

//Pass in your htmlstring, and the maximum width that you are allowing for the label
//this will, in turn, pass back the size of the newly created label via the binding 'size' variable
//you must use the new size variable frame on an encompassing view of wherever this htmlAttributedLabel now resides (like in an hstack, etc.)
struct htmlAttributedLabel: UIViewRepresentable {
    @Binding var htmlText: String
    var width: CGFloat
    @Binding var size:CGSize
    var lineLimit = 0
    //var textColor = Color(.label)
 
    func makeUIView(context: Context) -> UILabel {
        let label = UILabel()
        label.lineBreakMode = .byWordWrapping
        label.numberOfLines = lineLimit
        label.preferredMaxLayoutWidth = width
        //label.textColor = textColor.uiColor()
        return label
    }

    func updateUIView(_ uiView: UILabel, context: Context) {
    let htmlData = NSString(string: htmlText).data(using: String.Encoding.unicode.rawValue)
    let options = [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html]
    DispatchQueue.main.async {
        do {
            let attributedString = try NSMutableAttributedString(data: htmlData!, options: options, documentAttributes: nil)
            //add attributedstring attributes here if you want
            uiView.attributedText = attributedString
            size = uiView.sizeThatFits(CGSize(width: width, height: CGFloat.greatestFiniteMagnitude))
            print("htmlAttributedLabel size: \(size)")
        } catch {
            print("htmlAttributedLabel unexpected error: \(error).")
        }
    }
}

现在,要有效地使用此标签,您需要为其提供最大宽度,您可以从几何 reader 中获取该宽度。您还需要传入 CGSize 绑定,以便标签可以告诉父视图它需要呈现多少 space。您将依次使用此尺寸来设置包围视图高度,以便 swiftUI 的其余部分可以适当地围绕您的 html 标签进行布局:

@State var htmlText = "Hello,<br />I am <b>HTML</b>!"
@State var size:CGSize = .zero
var body: some View {
    HStack {
        GeometryReader { geometry in
                                htmlAttributedLabel(htmlText: $htmlText, width: geometry.size.width, size: $size).frame(width:size.width, height: size.height). //the frame is important to set here, otherwise sometimes it won't render right on repeat loads, depending on how this view is presented
                       }
           }.frame(height: size.height) //most important, otherwise swiftui won't really know how to layout things around your attributed label
}

您还可以设置行数限制或文本颜色等,显然您可以扩展此对象以接收您想要使用的任何 UIlabel 参数。

有些人建议使用 WKWebViewUILabel,但这些解决方案非常慢或不方便。我找不到原生的 SwiftUI 解决方案,所以我实现了自己的 (AttributedText)。它非常简单,功能有限,但运行速度很快,满足了我的需求。您可以在 README.md 文件中查看所有功能。如果现有功能对您来说不够用,请随时贡献力量。

代码示例

AttributedText("This is <b>bold</b> and <i>italic</i> text.")

结果

iOS 15(测试版)

文本现在支持基本的 Markdown!

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Regular")
            Text("*Italics*")
            Text("**Bold**")
            Text("~Strikethrough~")
            Text("`Code`")
            Text("[Link](https://apple.com)")
            Text("***[They](https://apple.com) ~are~ `combinable`***")
        }
    }
}

结果:


但是,如果您将包含 Markdown 的 String 存储在 属性 中,它不会呈现。我很确定这是一个错误。

struct ContentView: View {
    @State var textWithMarkdown = "***[They](https://apple.com) ~are~ `combinable`***"
    var body: some View {
        Text(textWithMarkdown)
    }
}

结果:

您可以通过使用 init(markdown:options:baseURL:).

textWithMarkdown 转换为 AttributedString 来解决此问题
struct ContentView: View {
    @State var textWithMarkdown = "***[They](https://apple.com) ~are~ `combinable`***"
    var body: some View {
        Text(textWithMarkdown.markdownToAttributed()) /// pass in AttributedString to Text
    }
}

extension String {
    func markdownToAttributed() -> AttributedString {
        do {
            return try AttributedString(markdown: self) /// convert to AttributedString
        } catch {
            return AttributedString("Error parsing markdown: \(error)")
        }
    }
}

结果: