How to Show HTML or Markdown in a Swiftui Text

How to show HTML or Markdown in a SwiftUI Text?

Text can just display Strings.
You can use a UIViewRepresentable with an UILabel and attributedText.

Probably attributedText text support will come later for SwiftUI.Text.

How to render HTML String in SwiftUI?

WKWebView has a scroll view in it and you also have the HTMLStringView wrapped in a ScrollView, leading to some layout conflicts.

Options:

  1. Remove the outer ScrollView:
var body : some View{
HTMLStringView(htmlContent: "<p>\(longDetails)</p>")
}

  1. Pass an explicit height:
var body : some View{
ScrollView{
ZStack{
HTMLStringView(htmlContent: "<p>\(longDetails)</p>")
.frame(height: 200)
}.padding()
}.padding(.top,50)
}

What you choose may vary based on your use case. You may also want to explore .isScrollEnabled on WKWebView if you don't want the WKWebView to be scrollable. And, it seems like you may also end up with a case where you need to find the height of the WKWebView content: How to determine the content size of a WKWebView?

SwiftUI Text Markdown dynamic string not working

you can try this taken from: How to show HTML or Markdown in a SwiftUI Text? halfway down the page.

extension String {
func markdownToAttributed() -> AttributedString {
do {
return try AttributedString(markdown: self) /// convert to AttributedString
} catch {
return AttributedString("Error parsing markdown: \(error)")
}
}
}
struct ContentView: View {
let linkTitle = "Apple Link"
let linkURL = "https://www.apple.com"
let string = "[Apple Link](https://www.apple.com)"

@State var textWithMarkdown = "[Apple Link](https://www.apple.com)"

var body: some View {
VStack {

Text(textWithMarkdown.markdownToAttributed()) // <-- this works

Text(string) // Not working

Text("[Apple Link](http://www.apple.com)") // Working

Text("[\(linkTitle)](http://www.apple.com)") // Working

Text("[\(linkTitle)](\(linkURL))") // Not working
}

}
}

How to show attributed string from local json file data to SwiftUI detail view or WKWebView?

to "...show exactly html formatted text with html properties applied...", try this function that uses AttributedString:
(note, this is particular for this text type, Gujarati?)

func attributedString(from str: String) -> AttributedString {
if let theData = str.data(using: .utf16) {
do {
let theString = try NSAttributedString(data: theData, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil)
return AttributedString(theString)
} catch { print("\(error)") }
}
return AttributedString(str)
}

and replace Text(item.title) with Text(attributedString(from: item.title))

You can also look at this SO post/anwser: Convert html links inside text to clickable links - SwiftUI

Convert html links inside text to clickable links - SwiftUI

What you have is an html string. You can interpret your html string using NSAttributedString initializer options NSAttributedString.DocumentType.html and initialize a new AttributedString with it. No need to manipulate and/or manually parse your string:


First add this extension to your project:

extension StringProtocol {
func htmlToAttributedString() throws -> AttributedString {
try .init(
.init(
data: .init(utf8),
options: [
.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue
],
documentAttributes: nil
)
)
}
}

Then extend Text to add a custom initializer:

extension Text {
init(html: String, alert: String? = nil) {
do {
try self.init(html.htmlToAttributedString())
} catch {
self.init(alert ?? error.localizedDescription)
}
}
}


import SwiftUI

struct ContentView: View {

var html = #"with banks (for example to the sometimes controversial but leading exchange <a href="https://www.coingecko.com/en/exchanges/bitfinex">Bitfinex</a>. For more info <a href="https://www.google.com/">Google</a>).""#

var body: some View {
Text(html: html)
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

SwiftUI Text - how can I create a hyperlink and underline a weblink in a string

This was the final solution I used. Should work for a variety of string inputs.

import SwiftUI

struct HyperlinkAndUnderlineTextView: View {

var body: some View {

ScrollView {

VStack (alignment: .leading, spacing: 30) {

Group {
CustomTextWithHyperlinkAndUnderline("Test of a hyperlink www.google.co.uk within a text message", .blue)
CustomTextWithHyperlinkAndUnderline("www.google.co.uk hyperlink at the start of a text message", .blue)
CustomTextWithHyperlinkAndUnderline("Test of hyperlink at the end of a text message www.google.co.uk", .blue)
CustomTextWithHyperlinkAndUnderline("www.google.co.uk", .blue)
CustomTextWithHyperlinkAndUnderline("This is 1 hyperlink www.google.co.uk. This is a 2nd hyperlink www.apple.com", .blue)
CustomTextWithHyperlinkAndUnderline("This is 1 hyperlink www.google.co.uk. This is a 2nd hyperlink www.apple.com. This is text after it.", .blue)
CustomTextWithHyperlinkAndUnderline("This is 1 hyperlink www.google.co.uk. This is a 2nd hyperlink www.apple.com. This is a 3rd hyperlink www.microsoft.com", .blue)
CustomTextWithHyperlinkAndUnderline("This is 1 hyperlink www.google.co.uk. This is a 2nd hyperlink www.apple.com. This is a 3rd hyperlink www.microsoft.com. This is text after it.", .blue)
CustomTextWithHyperlinkAndUnderline("www.google.co.uk is a hyperlink at the start of a text message. www.apple.com is the 2nd hyperlink within the same text message.", .blue)
CustomTextWithHyperlinkAndUnderline("This is a test of another type of url which will get processed google.co.uk", .blue)
}

Group {
CustomTextWithHyperlinkAndUnderline("google.co.uk", .blue)
CustomTextWithHyperlinkAndUnderline("Pure text with no hyperlink", .blue)
CustomTextWithHyperlinkAndUnderline("Emoji test quot;, .blue)
}
}
}
}
}

struct SplitMessageContentWithType {
var content: String = ""
var type: contentType = .text
}

enum contentType {
case text
case url
}

//Function to produce a text view where all urls and clickable and underlined
func CustomTextWithHyperlinkAndUnderline(_ inputString: String, _ underlineColor: Color) -> Text {

let inputText: [SplitMessageContentWithType] = splitMessage(inputString)


var output = Text("")

for input in inputText {
let text: Text

text = Text(.init(input.content))
.underline(input.type == .url ? true : false, color: underlineColor)

output = output + text
}

return output

}

func splitMessage(_ inputString: String) -> [SplitMessageContentWithType] {


//1) Function to detect if the input string contains any urls and returns the ones found as an array of strings
func detectIfInputStringContainsUrl(inputString: String) -> [String] {

let urlDetector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let matches = urlDetector.matches(in: inputString, options: [], range: NSRange(location: 0, length: inputString.utf16.count))

var urls: [String] = []

for match in matches {
guard let range = Range(match.range, in: inputString) else { continue }
let url = inputString[range]

urls.append(String(url))
}
return urls

}
let urlsFoundInInputString = detectIfInputStringContainsUrl(inputString: inputString)
print("\n \nurlsFoundInInputString are: \(urlsFoundInInputString)")



//2) Function to get the string components either side of a url from the inputString. Returns these components as an array of strings
func getStringComponentsSurroundingUrls(urlsFoundInInputString: [String]) -> [String] {

var stringComponentsSurroundingUrls: [String] = []

for (index, url) in urlsFoundInInputString.enumerated() {

let splitInputString = inputString.components(separatedBy: url)

//This code handles the case of an input string with 2 hyperlinks inside it (e.g. This is 1 hyperlink www.google.co.uk. This is a 2nd hyperlink www.apple.com. This is text after it.)
//In the 1st pass of the for loop, this will return splitInputString = ["This is 1 hyperlink ", ". This is a 2nd hyperlink www.apple.com. This is text after it."]
//Because the last element in the array contains either "www" or "http", we only append the contents of the first (prefix(1)) to stringComponentsSurroundingUrls (i.e "This is 1 hyperlink ")
//In the 2nd pass of the for loop, this will return splitInputString = ["This is 1 hyperlink www.google.co.uk. This is a 2nd hyperlink ", ". This is text after it."]
//Beacuse the last element in the array does not contain a hyperlink, we append both elements to stringComponentsSurroundingUrls
if splitInputString.last!.contains("www") || splitInputString.last!.contains("http") {
stringComponentsSurroundingUrls.append(contentsOf: inputString.components(separatedBy: url).prefix(1))
} else {
stringComponentsSurroundingUrls.append(contentsOf: inputString.components(separatedBy: url))
}


//At this point in the code, in the above example, stringComponentsSurroundingUrls = ["This is 1 hyperlink ",
// "This is 1 hyperlink www.google.co.uk. This is a 2nd hyperlink ",
// ". This is text after it."]
//We now iterate through this array of string, to complete another split. This time we separate out by any elements by urlsFoundInInputString[index-1]
//At the end of this for loop, stringComponentsSurroundingUrls = ["This is 1 hyperlink ",
// ". This is a 2nd hyperlink ",
// ". This is text after it."]
if index == urlsFoundInInputString.count - 1 {
for (index, stringComponent) in stringComponentsSurroundingUrls.enumerated() {
if index != 0 {
let stringComponentFurtherSeparated = stringComponent.components(separatedBy: urlsFoundInInputString[index-1])
stringComponentsSurroundingUrls.remove(at: index)
stringComponentsSurroundingUrls.insert(stringComponentFurtherSeparated.last!, at: index)
}
}
}
}

return stringComponentsSurroundingUrls
}

var stringComponentsSurroundingUrls: [String]
//If there no no urls found in the inputString, simply set stringComponentsSurroundingUrls equal to the input string as an array, else call the function to find the string comoponents surrounding the Urls found
if urlsFoundInInputString == [] {
stringComponentsSurroundingUrls = [inputString]
} else {
stringComponentsSurroundingUrls = getStringComponentsSurroundingUrls(urlsFoundInInputString: urlsFoundInInputString)
}
print("\n \nstringComponentsSurroundingUrls are: \(stringComponentsSurroundingUrls)")




//3)Function to markdown the urls found to follow a format of [placeholderText](hyperlink) such as [Google](https://google.com) so SwiftUI markdown can render it as a hyperlink
func markdown(urlsFoundInInputString: [String]) -> [String] {

var markdownUrlsArray: [String] = []

for url in urlsFoundInInputString {
let placeholderText = "[\(url)]"

var hyperlink: String
if url.hasPrefix("https://www.") {
hyperlink = "(\(url.replacingOccurrences(of: "https://www.", with: "https://")))"
} else if url.hasPrefix("www.") {
hyperlink = "(\(url.replacingOccurrences(of: "www.", with: "https://")))"
} else {
hyperlink = "(http://\(url))"
}

let markdownUrl = placeholderText + hyperlink

markdownUrlsArray.append(markdownUrl)
}

return markdownUrlsArray

}

let markdownUrls = markdown(urlsFoundInInputString: urlsFoundInInputString)
print("\n \nmarkdownUrls is: \(markdownUrls)")



//4) Function to combine stringComponentsSurroundingUrls and markdownUrls back together
func recombineStringComponentsAndMarkdownUrls(stringComponentsSurroundingUrls: [String], markdownUrls: [String]) -> [SplitMessageContentWithType] {

var text = SplitMessageContentWithType()
var text2 = SplitMessageContentWithType()
var splitMessageContentWithTypeAsArray: [SplitMessageContentWithType] = []

//Saves each string component and url as either .text or .url type so in the CustomTextWithHyperlinkAndUnderline() function, we can underline all .url types
for (index, stringComponents) in stringComponentsSurroundingUrls.enumerated() {
text.content = stringComponents
text.type = .text
splitMessageContentWithTypeAsArray.append(text)

if index <= (markdownUrls.count - 1) {
text2.content = markdownUrls[index]
text2.type = .url
splitMessageContentWithTypeAsArray.append(text2)
}
}

return splitMessageContentWithTypeAsArray
}


let recombineStringComponentsAndMarkdownUrls = recombineStringComponentsAndMarkdownUrls(stringComponentsSurroundingUrls: stringComponentsSurroundingUrls, markdownUrls: markdownUrls)
print("\n \nrecombineStringComponentsAndMarkdownUrls is: \(recombineStringComponentsAndMarkdownUrls)")

return recombineStringComponentsAndMarkdownUrls

}

iOS 15 beta 4: AttributedString with Markdown not rendering in Text in SwiftUI?

If you pass Markdown directly into a Text.init(), SwiftUI will auto-convert it into an AttributedString.

However, to go from a Markdown string to an AttributedString, you need to use an explicit AttributedString(markdown:options:baseURL:) initialiser. For example:

var text = try! AttributedString(markdown: "**Hello**, `world`! Visit our [website](https://www.capitalone.com).”)

Note that this initialiser throws if the conversion can’t be made correctly. I’ve used try! here because your example Markdown will definitely convert, but depending on the source of the Markdown text you may want to handle a thrown error a little more intelligently.

SwiftUI Text Markdown support for string variables or string interpolation not working

After test and fail moments, I found that Text only handles markdown when string literal is passed to initializer. as below:

to achieve the needed behavior:

let text: String = "**Hello** *World*"

Text("**Hello** *World*") // will be rendered with markdown formatting
Text(text) // will NOT be rendered according to markdown
Text(.init(text)) // will be rendered with markdown formatting

Sample Image

Swift: Display HTML data in a label or textView

For Swift 5:

extension String {
var htmlToAttributedString: NSAttributedString? {
guard let data = data(using: .utf8) else { return nil }
do {
return try NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding:String.Encoding.utf8.rawValue], documentAttributes: nil)
} catch {
return nil
}
}
var htmlToString: String {
return htmlToAttributedString?.string ?? ""
}
}

Then, whenever you want to put HTML text in a UITextView use:

textView.attributedText = htmlText.htmlToAttributedString


Related Topics



Leave a reply



Submit