【问题标题】:How to show HTML or Markdown in a SwiftUI Text?如何在 SwiftUI 文本中显示 HTML 或 Markdown?
【发布时间】:2019-11-15 11:25:32
【问题描述】:

如何设置 SwiftUI Text 以显示渲染的 HTML 或 Markdown?

类似这样的:

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

或者对于医学博士:

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

也许我需要不同的视图?

【问题讨论】:

    标签: html swift text markdown swiftui


    【解决方案1】:

    如果您不需要专门使用文本视图。您可以创建一个显示 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)
        }
    }
    

    在你的身体里简单地像这样调用这个对象:

    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()
        }
    }
    

    【讨论】:

    • 我的要求是使用 swiftui 将 html 数据与项目列表的其他文本数据一起显示。但是,每当我尝试执行上述代码时,我都看不到任何视图。请告诉我可能是什么原因。
    • 嗨@DJ,它正在处理我的项目,我已经用完整的 SwiftUI 文件更新了我的答案。我的意思是,你不会在“预览屏幕”上看到任何东西,但如果你按下播放就可以了。如果我回答了你的问题,请告诉我。
    • 感谢您的回复,它也有效,但不在列表中。我认为这可能是列表中的大小问题。我会尝试进一步调查。
    • @DJ- 我尝试使用 UIViewRepresentable 属性多行文本。我能够从 GeometryReader 宽度中获取属性和多行文本标签,以设置 PreferredMaxLayoutWidth。但是列表项大小调整文本与其他项目重叠的问题。如果您找到解决方案,请添加答案,在此先感谢。
    • 在此处查看更改。这对我来说是固定的。 developer.apple.com/forums/thread/653935
    【解决方案2】:

    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)")
            }
        }
    }
    

    结果:

    【讨论】:

    • 太棒了!但它不起作用,如果你把一个包含降价的字符串放在一个变量中!有解决方案还是只是提交的错误?
    • @gundrabur 很可能是一个错误(我记得有人在 WWDC21 数字休息室问过这个问题)。请参阅我的编辑以了解解决方法
    • @aheze Markdown 仅适用于字符串文字,请参阅this tweet
    • 要解决一个未转换为 Markdown 的存储字符串,而不是转换为 AttributedString,您可以简单地从字符串值创建一个 LocalizedStringKey 并用它初始化 Text 视图LocalizedStringKey。即Text(LocalizedStringKey(textWithMarkdown))
    • 我通过使用Text(.init(yourTextVariable)) 解决了这个问题。不需要markdownToAttributed 函数。查看答案:stackoverflow.com/a/69898689/7653367
    【解决方案3】:

    由于我找到了另一种解决方案,因此我想与您分享。

    创建一个新的 View Representable

    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>")
    

    【讨论】:

    • 如何增加字体大小?
    • 嗨@DiNerd,在 NSAttributedString 的参数“options:”中,您应该为字体添加一个新选项,如下所示: NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType .html, .font: UIFont.boldSystemFont(ofSize: 36)], documentAttributes: nil)
    • 当文本不适合一行时,您会使用谁?我添加了这些行,但它不起作用: label.lineBreakMode = .byWordWrapping, label.numberOfLines = 0
    • 嗨@Ramis看看这个答案我认为可以帮助stackoverflow.com/a/58474880/129889
    • 非常感谢!我发现标签的宽度有问题,它是水平扩展而不是垂直扩展。原来这是因为标签在 ScrollView 内。如果有人有同样的问题,这里的答案有助于解决这个问题:stackoverflow.com/a/62788230/408286
    【解决方案4】:

    Text 只能显示Strings。 您可以将UIViewRepresentableUILabelattributedText 一起使用。

    SwiftUI.Text 可能会在稍后提供属性文本文本支持。

    【讨论】:

      【解决方案5】:

      你可以尝试使用 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。

      【讨论】:

      • 能否请您举例说明如何使用您的代码?我试过这个没有成功: TextWithAttributedString(attributedString: DownView(frame: .zero, markdownString: "").accessibilityAttributedValue!) .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)跨度>
      • 您能告诉我们如何称呼它吗?我们可以说 TextWithAttributedString(attributedString:"
        Hello check
        ")
      • 是的,它原本打算使用TextWithAttributedString(attributedString:"# Hello SwiftUI") 来调用它,但与此同时我切换到了另一种方法,它实际上显示了一些东西,但还不是最优的。如果我取得真正的进展,我会在这里发布一个新的答案。
      • @blackjacx - 我尝试使用 UIViewRepresentable 属性多行文本。我能够获得属性和多行文本标签。从 GeometryReader 宽度设置标签的 preferredMaxLayoutWidth。但是列表项大小调整文本与其他项目重叠的问题。如果您找到解决方案,请添加答案,在此先感谢。
      • @blackjacx 这不会转换 MD 或 HTML - 只是在标签中输出原始字符串 - 我错过了什么?
      【解决方案6】:

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

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

      随意贡献!

      【讨论】:

      • 你还在维护这个吗?我可能会为它做出贡献,但目前它无法在 iOS 上编译,并且有一个拉取请求修复它等待合并。
      【解决方案7】:

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

      代码示例

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

      结果

      【讨论】:

        【解决方案8】:

        就在 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).")
                }
            }
        }
        

        现在,要有效地使用此标签,您需要为其提供一个最大宽度,您可以从几何阅读器中获得该宽度。您还需要传入一个 CGSize 绑定,以便标签可以告诉父视图它需要渲染多少空间。反过来,您将使用此大小设置包含视图的高度,以便 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 参数。

        【讨论】:

        • 这很好用,但我试图在其中添加字体,但没有运气,有什么建议吗?谢谢。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-21
        • 2021-07-18
        • 2020-03-28
        • 2021-05-13
        • 2017-05-27
        • 1970-01-01
        相关资源
        最近更新 更多