Skip to content

Instantly share code, notes, and snippets.

@fxm90
Last active June 28, 2021 07:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fxm90/fc977d346d2372cfdad11bc822b69a82 to your computer and use it in GitHub Desktop.
Save fxm90/fc977d346d2372cfdad11bc822b69a82 to your computer and use it in GitHub Desktop.
Extension that converts Strings with basic HTML tags to SwiftUI's Text (Supports SwiftUI 1.0 / iOS 13.0).
//
// Font+Formatting.swift
//
// Created by Felix Mau on 12.11.20.
// Copyright © 2020 Felix Mau. All rights reserved.
//
import Foundation
import SwiftUI
/// The supported tags inside a string we can format.
enum TextFormattingTag: String {
case strong
case em
// MARK: - Public properties
var openingTag: String {
"<\(rawValue)>"
}
var closingTag: String {
"</\(rawValue)>"
}
}
extension Text {
/// A text view that displays a string literal.
/// Furthermore the text between the given `formattingTag` will be modified` using the given `fontModifier`.
///
/// - Parameters:
/// - text: The string to modify, e.g. `<strong>Lorem</strong> ipsum dolor sit <strong>amet</strong>`
/// - formattingTag: The tag to search for e.g. `.strong`
/// - fontModifier: The closure to be applied to the text between the given formatting tag.
///
/// - Returns: A formatted `Text` with the applied font modifier between the text.
///
/// - Note: Currently we only support converting one formatting tag per call.
init(_ text: String, replaceTag formattingTag: TextFormattingTag, with fontModifier: (Text) -> Text) {
let textSeparatedByTags = text.separatedBy(openingTag: formattingTag.openingTag,
closingTag: formattingTag.closingTag)
// Even with an empty string, we'll always have at least one entry.
// Therefore we don't have to check the length of the array here.
let firstText = Text(textSeparatedByTags[0].text)
// Append and format all remaining tags.
self = textSeparatedByTags
.dropFirst()
.reduce(firstText) { res, textSeparatedByTag in
var textForCurrentTag = Text(textSeparatedByTag.text)
if textSeparatedByTag.isBetweenTags {
textForCurrentTag = fontModifier(textForCurrentTag)
}
return res + textForCurrentTag
}
}
}
// MARK: - Helpers
private extension String {
// MARK: - Types
struct TagSeparated {
let text: String
let isBetweenTags: Bool
}
// MARK: - Public methods
/// Splits the current string via the opening- and closing-tag.
///
/// E.g. having `self` set to `Lorem <strong>Ipsum</strong> Dolor`, `openingTag` set to `<strong>` and `closingTag` set to `</strong>`
/// will produce the following result:
///
/// 0: `TagSeparated(text: "Lorem ", isBetweenTags: false)`
/// 1: `TagSeparated(text: "Ipsum", isBetweenTags: true)`
/// 2: `TagSeparated(text: " Dolor", isBetweenTags: false)`
func separatedBy(openingTag: String, closingTag: String) -> [TagSeparated] {
var result = [TagSeparated]()
// Split via opening tag, e.g. "Lorem <strong>Ipsum</strong> Dolor" into:
// 0: "Lorem "
// 1: "Ipsum</strong> Dolor"
let separatedByOpeningTags = components(separatedBy: openingTag)
separatedByOpeningTags.forEach { separatedByOpeningTag in
// Split via closing tag, e.g. "Ipsum</strong> Dolor" into:
// 0: "Ipsum"
// 1: " Dolor"
let separatedByClosingTags = separatedByOpeningTag.components(separatedBy: closingTag)
switch separatedByClosingTags.count {
case 1:
// Handle start of text e.g. "Lorem " of "Lorem <strong>Ipsum</strong>"
result.append(
TagSeparated(text: separatedByClosingTags[0], isBetweenTags: false)
)
case 2:
// Using the example shown above (line 98) this would contain "Ipsum".
result.append(
TagSeparated(text: separatedByClosingTags[0], isBetweenTags: true)
)
// Using the example shown above (line 99) this would contain " Dolor".
result.append(
TagSeparated(text: separatedByClosingTags[1], isBetweenTags: false)
)
default:
// We have an invalid formatted string, and therefore skip here.
break
}
}
return result
}
}
@fxm90
Copy link
Author

fxm90 commented Nov 14, 2020

The following code

Text("Lorem <em>Ipsum</em> Dolor", replaceTag: .strong, with: { $0.italic() })

will produce

Lorem Ipsum Dolor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment