How to show HTML or Markdown in a SwiftUI Text?
Text
can just display String
s.
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:
- Remove the outer
ScrollView
:
var body : some View{
HTMLStringView(htmlContent: "<p>\(longDetails)</p>")
}
- 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
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
Flexbox, Z-Index & Position: Static: Why Isn't It Working
Why Is "&Reg" Being Rendered as "®" Without The Bounding Semicolon
How to Make a Table with Equal Column Widths in CSS
Scrollbar Without Fixed Height/Dynamic Height with Scrollbar
Square Div Where Height Is Equal to Viewport
How to Validate Pattern Matching in Textarea
Diagonal Stripes That Are 1Px Wide
Why Does Width and Height of a Flex Item Affect How a Flex Item Is Rendered
Scrolling to an Anchor Using Transition/CSS3
Button Image as Form Input Submit Button
How to Make The Web Page Height to Fit Screen Height
Why Does CSS Grid Layout Add Extra Gaps Between Cells
HTML/CSS Set Div to Height of Sibling
Inline Style to Act as: Hover in CSS