Skip to content

Instantly share code, notes, and snippets.

@B0Y3R
Last active December 19, 2024 16:55
Show Gist options
  • Save B0Y3R/824a64d5647bfb15274774ba81b664b8 to your computer and use it in GitHub Desktop.
Save B0Y3R/824a64d5647bfb15274774ba81b664b8 to your computer and use it in GitHub Desktop.
Inline clickable highlightable text in swift
import Foundation
import SwiftUI
struct Highlight {
let text: String
let data: String
let onClick: (String) -> Void
}
struct HighlightedText: View {
private struct TextData {
let text: String
let tag: String?
let data: String?
let onClick: ((String) -> Void)?
}
let text: String
let highlights: [Highlight]
let infoTextColor: Color
let infoLinkTextColor: Color
let textStyle: Font // Simplified from TextStyle for this example
var body: some View {
let textData = processTextData()
Text(attributedString(from: textData))
}
private func processTextData() -> [TextData] {
var textData: [TextData] = []
if highlights.isEmpty {
textData.append(TextData(
text: text,
tag: nil,
data: nil,
onClick: nil
))
return textData
}
var startIndex = text.startIndex
for (i, highlight) in highlights.enumerated() {
guard let endIndex = text.range(of: highlight.text)?.lowerBound else {
fatalError("Highlighted text mismatch")
}
// Add text before highlight
if startIndex < endIndex {
textData.append(TextData(
text: String(text[startIndex..<endIndex]),
tag: nil,
data: nil,
onClick: nil
))
}
// Add highlighted text
textData.append(TextData(
text: highlight.text,
tag: "\(highlight.text)_TAG",
data: highlight.data,
onClick: highlight.onClick
))
startIndex = text.index(endIndex, offsetBy: highlight.text.count)
// Add remaining text after last highlight
if i == highlights.count - 1 && startIndex < text.endIndex {
textData.append(TextData(
text: String(text[startIndex...]),
tag: nil,
data: nil,
onClick: nil
))
}
}
return textData
}
private func attributedString(from textData: [TextData]) -> AttributedString {
var attributedString = AttributedString("")
for data in textData {
var portion = AttributedString(data.text)
if data.tag != nil && data.data != nil {
portion.foregroundColor = infoLinkTextColor
portion.link = URL(string: "action://\(data.data!)")
} else {
portion.foregroundColor = infoTextColor
}
attributedString += portion
}
return attributedString
}
}
#Preview {
VStack(spacing: 20) {
// Simple example
HighlightedText(
text: "Click here to learn more about our Terms and Conditions",
highlights: [
Highlight(
text: "Terms and Conditions",
data: "terms",
onClick: { data in
print("Clicked terms: \(data)")
}
)
],
infoTextColor: .black,
infoLinkTextColor: .blue,
textStyle: .body
)
// Multiple highlights example
HighlightedText(
text: "Please review our Privacy Policy and Terms of Service before continuing",
highlights: [
Highlight(
text: "Privacy Policy",
data: "privacy",
onClick: { data in
print("Clicked privacy: \(data)")
}
),
Highlight(
text: "Terms of Service",
data: "terms",
onClick: { data in
print("Clicked terms: \(data)")
}
)
],
infoTextColor: .gray,
infoLinkTextColor: .blue,
textStyle: .callout
)
// Different styling example
HighlightedText(
text: "Contact Support for help",
highlights: [
Highlight(
text: "Support",
data: "support",
onClick: { data in
print("Clicked support: \(data)")
}
)
],
infoTextColor: .black,
infoLinkTextColor: .green,
textStyle: .title3
)
}
.padding()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment