如何在 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
只能显示 String
s。
您可以将 UIViewRepresentable
与 UILabel
和 attributedText
.
一起使用
可能稍后 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 参数。
有些人建议使用 WKWebView 或 UILabel,但这些解决方案非常慢或不方便。我找不到原生的 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)")
}
}
}
结果:
如何设置 SwiftUI Text
以显示呈现的 HTML 或 Markdown?
像这样:
Text(HtmlRenderedString(fromString: "<b>Hi!</b>"))
或MD:
Text(MarkdownRenderedString(fromString: "**Bold**"))
也许我需要不同的视图?
Text
只能显示 String
s。
您可以将 UIViewRepresentable
与 UILabel
和 attributedText
.
可能稍后 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 参数。
有些人建议使用 WKWebView 或 UILabel,但这些解决方案非常慢或不方便。我找不到原生的 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)")
}
}
}
结果: