Div*_*Div 18 html markdown text swift swiftui
如何设置 SwiftUIText以显示呈现的 HTML 或 Markdown?
像这样的东西:
Text(HtmlRenderedString(fromString: "<b>Hi!</b>"))
Run Code Online (Sandbox Code Playgroud)
或对于 MD:
Text(MarkdownRenderedString(fromString: "**Bold**"))
Run Code Online (Sandbox Code Playgroud)
也许我需要一个不同的视图?
Tom*_*mas 16
如果您不需要专门使用文本视图。您可以创建一个显示 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)
}
}
Run Code Online (Sandbox Code Playgroud)
在你的身体中简单地调用这个对象,如下所示:
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()
}
}
Run Code Online (Sandbox Code Playgroud)
Ger*_*tan 11
从iOS 15开始,Text可以有一个AttributedString参数。
没有UIViewRepresentable必要
由于NSAttributedString可以从 HTML 创建,因此该过程很简单:
import SwiftUI
@available(iOS 15, *)
struct TestHTMLText: View {
var body: some View {
let html = "<h1>Heading</h1> <p>paragraph.</p>"
if let nsAttributedString = try? NSAttributedString(data: Data(html.utf8), options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil),
let attributedString = try? AttributedString(nsAttributedString, including: \.uiKit) {
Text(attributedString)
} else {
// fallback...
Text(html)
}
}
}
@available(iOS 15, *)
struct TestHTMLText_Previews: PreviewProvider {
static var previews: some View {
TestHTMLText()
}
}
Run Code Online (Sandbox Code Playgroud)
代码呈现如下:
ahe*_*eze 10
Text 现在支持基本的 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`***")
}
}
}
Run Code Online (Sandbox Code Playgroud)
结果:
但是,如果您String在属性中存储包含 Markdown 的 ,则它不会呈现。我很确定这是一个错误。
struct ContentView: View {
@State var textWithMarkdown = "***[They](https://apple.com) ~are~ `combinable`***"
var body: some View {
Text(textWithMarkdown)
}
}
Run Code Online (Sandbox Code Playgroud)
结果:
您可以通过转换textWithMarkdown为AttributedString,使用来解决此问题init(markdown:options:baseURL:)。
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)")
}
}
}
Run Code Online (Sandbox Code Playgroud)
结果:
由于我找到了另一个解决方案,我想与您分享。
创建一个新的可表示视图
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) {}
}
Run Code Online (Sandbox Code Playgroud)
然后像这样使用它:
HTMLText(html: "<h1>Your html string</h1>")
Run Code Online (Sandbox Code Playgroud)
您可以尝试使用包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))
}
}
Run Code Online (Sandbox Code Playgroud)
我在这方面取得了一定的成功,但无法正确获得标签子类的框架。也许我需要使用 GeometryReader 来实现这一点。
有些人建议使用WKWebView或UILabel,但这些解决方案非常慢或不方便。我找不到原生 SwiftUI 解决方案,因此我实现了自己的解决方案(AttributedText)。它非常简单,功能有限,但运行速度很快并且满足了我的需求。您可以在 README.md 文件中看到所有功能。如果现有功能不足以满足您的需要,请随时做出贡献。
代码示例
AttributedText("This is <b>bold</b> and <i>italic</i> text.")
Run Code Online (Sandbox Code Playgroud)
结果
Swift 5.7 带来了与正则表达式相关的新功能。RegexBuilder除了现有的正则表达式支持之外,还实现了新的支持,这使得推断 HTML 标记中的字符串变得更加容易。
只需很少的工作,我们就可以构建一个从“基本”HTML 代码到 Markdown 的转换器。我所说的“基本”是指:
当然,只要付出更多努力,任何事情都可以实现,但我将坚持使用基本示例。
扩展名String:
extension String {
func htmlToMarkDown() -> String {
var text = self
var loop = true
// Replace HTML comments, in the format <!-- ... comment ... -->
// Stop looking for comments when none is found
while loop {
// Retrieve hyperlink
let searchComment = Regex {
Capture {
// A comment in HTML starts with:
"<!--"
ZeroOrMore(.any, .reluctant)
// A comment in HTML ends with:
"-->"
}
}
if let match = text.firstMatch(of: searchComment) {
let (_, comment) = match.output
text = text.replacing(comment, with: "")
} else {
loop = false
}
}
// Replace line feeds with nothing, which is how HTML notation is read in the browsers
var text = self.replacing("\n", with: "")
// Line breaks
text = text.replacing("<div>", with: "\n")
text = text.replacing("</div>", with: "")
text = text.replacing("<p>", with: "\n")
text = text.replacing("<br>", with: "\n")
// Text formatting
text = text.replacing("<strong>", with: "**")
text = text.replacing("</strong>", with: "**")
text = text.replacing("<b>", with: "**")
text = text.replacing("</b>", with: "**")
text = text.replacing("<em>", with: "*")
text = text.replacing("</em>", with: "*")
text = text.replacing("<i>", with: "*")
text = text.replacing("</i>", with: "*")
// Replace hyperlinks block
loop = true
// Stop looking for hyperlinks when none is found
while loop {
// Retrieve hyperlink
let searchHyperlink = Regex {
// A hyperlink that is embedded in an HTML tag in this format: <a... href="<hyperlink>"....>
"<a"
// There could be other attributes between <a... and href=...
// .reluctant parameter: to stop matching after the first occurrence
ZeroOrMore(.any)
// We could have href="..., href ="..., href= "..., href = "...
"href"
ZeroOrMore(.any)
"="
ZeroOrMore(.any)
"\""
// Here is where the hyperlink (href) is captured
Capture {
ZeroOrMore(.any)
}
"\""
// After href="<hyperlink>", there could be a ">" sign or other attributes
ZeroOrMore(.any)
">"
// Here is where the linked text is captured
Capture {
ZeroOrMore(.any, .reluctant)
}
One("</a>")
}
.repetitionBehavior(.reluctant)
if let match = text.firstMatch(of: searchHyperlink) {
let (hyperlinkTag, href, content) = match.output
let markDownLink = "[" + content + "](" + href + ")"
text = text.replacing(hyperlinkTag, with: markDownLink)
} else {
loop = false
}
}
return text
}
}
Run Code Online (Sandbox Code Playgroud)
用法:
HTML 文本:
let html = """
<div>You need to <b>follow <i>this</i> link</b> here: <a href="https://example.org/en">sample site</a></div>
"""
Run Code Online (Sandbox Code Playgroud)
降价转换:
let markdown = html.htmlToMarkDown()
print(markdown)
// Result:
// You need to **follow *this* link** here: [sample site](https://example.org/en)
Run Code Online (Sandbox Code Playgroud)
在 SwiftUI 中:
Text(.init(markdown))
Run Code Online (Sandbox Code Playgroud)
你看到什么了:
Text只能显示Strings。您可以将 aUIViewRepresentable与UILabeland一起使用attributedText。
可能稍后会为SwiftUI.Text.
iOS 15 支持基本 Markdown,但不包括标题或图像。如果您想在文本中包含基本标题和图像,这里有一个答案:
Text("Body of text here with **bold** text") // This will work as expected
Run Code Online (Sandbox Code Playgroud)
但:
let markdownText = "Body of text here with **bold** text".
Text(markdownText) // This will not render the markdown styling
Run Code Online (Sandbox Code Playgroud)
但您可以通过执行以下操作来解决此问题:
Text(.init(markdownText)) // This will work as expected, but you won't see the headings formatted
Run Code Online (Sandbox Code Playgroud)
但是 SwiftUI markdown 不支持标题(#、##、### 等),因此如果您希望"# heading \nBody of text here with **bold** text"所有内容都能正确呈现,减去标题,您仍然会看到“# header”。
因此,一种解决方案是将字符串分成几行,并实现一个ForEach循环来检查标题前缀 (#),删除#, 并创建一个Text()具有适当样式的元素,如下所示:
let lines = blogPost.blogpost.components(separatedBy: .newlines)
VStack(alignment: .leading) {
ForEach(lines, id: \.self) { line in
if line.hasPrefix("# ") {
Text(line.dropFirst(2))
.font(.largeTitle)
.fontWeight(.heavy)
} else if line.hasPrefix("## ") {
Text(line.dropFirst(3))
.font(.title)
.fontWeight(.heavy)
} else if line.hasPrefix("### ") {
Text(line.dropFirst(4))
.font(.headline)
.fontWeight(.heavy)
} else {
Text(.init(line))
.font(.body)
}
}
}
Run Code Online (Sandbox Code Playgroud)
这将创建一个格式良好的 Markdown 文本,包括标题。
如果我们还想添加图像,首先我们可以在 URL 属性上创建一个扩展:
extension URL {
func isImage() -> Bool {
let imageExtensions = ["jpg", "jpeg", "png", "gif"]
return imageExtensions.contains(self.pathExtension.lowercased())
}
}
Run Code Online (Sandbox Code Playgroud)
此方法检查 URL 的路径扩展名是否是常见图像文件扩展名(jpg、jpeg、png 或 gif)之一,如果是,则返回 true。
然后,我们可以像这样改变 ForEach 循环:
let lines = blogPost.blogpost.components(separatedBy: .newlines)
ForEach(lines, id: \.self) { line in
if line.hasPrefix("# ") {
Text(line.dropFirst(2))
.font(.largeTitle)
.fontWeight(.heavy)
} else if line.hasPrefix("## ") {
Text(line.dropFirst(3))
.font(.title)
.fontWeight(.heavy)
} else if line.hasPrefix("### ") {
Text(line.dropFirst(4))
.font(.headline)
.fontWeight(.heavy)
} else if let imageUrl = URL(string: line), imageUrl.isImage() {
// If the line contains a valid image URL, display the image
AsyncImage(url: imageUrl) { phase in
switch phase {
case .empty:
ProgressView()
case .success(let image):
image
.resizable()
.aspectRatio(contentMode: .fit)
case .failure:
Text("Failed to load image")
@unknown default:
fatalError()
}
}
} else {
Text(line)
.font(.body)
}
}
Run Code Online (Sandbox Code Playgroud)
在此更新的代码中,我们尝试使用 URL(string: line) 从该行创建 URL 对象,然后在生成的 URL 上调用自定义扩展方法 isImage() 来检查该行是否包含有效的图像 URL。如果它指向一个图像。
如果该行包含有效的图像 URL,我们将使用 AsyncImage 视图从 URL 异步加载图像。AsyncImage 视图自动处理图像的加载和缓存,并在加载图像时提供占位符 ProgressView。加载图像后,我们使用图像视图以及 ressized() 和aspectRatio(contentMode: .fit) 修饰符来显示它,以适当地调整图像大小和缩放图像。如果图像由于某种原因无法加载,我们会显示一条错误消息。
| 归档时间: |
|
| 查看次数: |
11853 次 |
| 最近记录: |