Last active
January 27, 2026 16:21
-
-
Save fxm90/abd949e4258050f2f3cd80118024e5bd to your computer and use it in GitHub Desktop.
Extension that converts Strings with basic HTML tags to SwiftUI's Text (Supports SwiftUI 3.0 / iOS 15.0).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // SwiftUI+HTML.swift | |
| // | |
| // Created by Felix Mau on 28.05.21. | |
| // Copyright © 2021 Felix Mau. All rights reserved. | |
| // | |
| import SwiftUI | |
| /// A lightweight utility that converts a limited subset of HTML tags | |
| /// into their Markdown equivalents. | |
| /// | |
| /// - SeeAlso: ``HTMLToMarkdownConverter.Tag`` for the list of supported tags. | |
| @available(iOS 15.0, *) | |
| enum HTMLToMarkdownConverter { | |
| // MARK: - Public Methods | |
| /// Converts supported HTML tags in the given string into their | |
| /// corresponding Markdown syntax. | |
| /// | |
| /// - Parameter html: A string containing HTML markup. | |
| /// | |
| /// - Returns: A Markdown-formatted string representing the supported | |
| /// HTML tags found in the input. | |
| /// | |
| /// - SeeAlso: ``HTMLToMarkdownConverter.Tag`` for the list of supported tags. | |
| static func convert(_ html: String) -> String { | |
| // Convert "basic" HTML tags that don't use an attribute. | |
| let markdown = Tag.allCases.reduce(html) { result, tag in | |
| result | |
| .replacingOccurrences(of: tag.openingHtmlTag, with: tag.markdownDelimiter) | |
| .replacingOccurrences(of: tag.closingHtmlTag, with: tag.markdownDelimiter) | |
| } | |
| // Anchor tags (`<a>`) use an attribute and therefore needs to be handled differently. | |
| return convertAnchorTagsToMarkdown(markdown) | |
| } | |
| // MARK: - Private Methods | |
| /// Converts HTML anchor (`<a>`) tags into their Markdown representation. | |
| /// | |
| /// Only the following syntax is supported: | |
| /// `<a href="URL">TEXT</a>` → `[TEXT](URL)` | |
| /// | |
| /// Anchor tags with additional attributes (e.g. `target`, `rel`, nested elements, or multiline content) | |
| /// are **not supported** and may produce incorrect results. | |
| /// | |
| /// - Parameter html: A string potentially containing HTML anchor tags. | |
| /// | |
| /// - Returns: A string where supported HTML links are replaced with Markdown links. | |
| private static func convertAnchorTagsToMarkdown(_ html: String) -> String { | |
| html.replacingOccurrences( | |
| of: "<a href=\"(.+)\">(.+)</a>", | |
| with: "[$2]($1)", | |
| options: .regularExpression, | |
| range: nil, | |
| ) | |
| } | |
| } | |
| // MARK: - Supporting Types | |
| extension HTMLToMarkdownConverter { | |
| /// A supported inline HTML tag that can be converted to Markdown. | |
| /// | |
| /// Each case represents an HTML tag whose opening and closing tags | |
| /// are replaced with a corresponding Markdown delimiter. | |
| enum Tag: String, CaseIterable { | |
| case strong | |
| case em | |
| case s | |
| case code | |
| /// The opening HTML tag (e.g. `<strong>`). | |
| var openingHtmlTag: String { | |
| "<\(rawValue)>" | |
| } | |
| /// The closing HTML tag (e.g. `</strong>`). | |
| var closingHtmlTag: String { | |
| "</\(rawValue)>" | |
| } | |
| /// The Markdown delimiter corresponding to the HTML tag. | |
| var markdownDelimiter: String { | |
| switch self { | |
| case .strong: | |
| "**" | |
| case .em: | |
| "*" | |
| case .s: | |
| "~~" | |
| case .code: | |
| "`" | |
| } | |
| } | |
| } | |
| } | |
| @available(iOS 15.0, *) | |
| extension Text { | |
| // MARK: - Initializer | |
| /// Creates a `Text` view by rendering a string containing supported HTML tags. | |
| /// | |
| /// The HTML is first converted to Markdown using ``HTMLToMarkdownConverter``, | |
| /// then parsed into an `AttributedString`. | |
| /// | |
| /// - Parameter html: A string containing supported HTML markup. | |
| /// | |
| /// - Note: If Markdown parsing fails, the initializer falls back | |
| /// to rendering the raw Markdown string without formatting. | |
| /// | |
| /// - SeeAlso: ``HTMLToMarkdownConverter.Tag`` for the list of supported tags. | |
| init(html: String) { | |
| let markdown = HTMLToMarkdownConverter.convert(html) | |
| do { | |
| let markdownAsAttributedString = try AttributedString(markdown: markdown) | |
| self = .init(markdownAsAttributedString) | |
| } catch { | |
| print("⚠️ – Couldn't parse markdown:", error) | |
| // Render the raw Markdown string without formatting as fallback. | |
| self = .init(markdown) | |
| } | |
| } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Related test when using Swift Testing