Skip to content

Instantly share code, notes, and snippets.

@natebird
Last active September 15, 2024 14:39
Show Gist options
  • Save natebird/6215668b95fb6dd728c2fe403f412fd7 to your computer and use it in GitHub Desktop.
Save natebird/6215668b95fb6dd728c2fe403f412fd7 to your computer and use it in GitHub Desktop.
Reusable view modifier to make URLs in a string clickable in SwiftUI
import SwiftUI
struct ClickableLinksModifier: ViewModifier {
let text: String
func body(content: Content) -> some View {
let attributedString = parseURLs(in: text)
VStack {
ForEach(0..<attributedString.count, id: \.self) { index in
if let url = attributedString[index].url {
Link(attributedString[index].text, destination: url)
.foregroundColor(.blue)
.underline()
.frame(maxWidth: .infinity, alignment: .leading)
} else {
Text(attributedString[index].text)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
}
// Helper function to parse the URLs and split text into parts
private func parseURLs(in text: String) -> [(text: String, url: URL?)] {
var parts: [(String, URL?)] = []
let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let matches = detector?.matches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count)) ?? []
var lastRangeEnd = text.startIndex
for match in matches {
if let range = Range(match.range, in: text), let url = match.url {
// Add the non-link text before the URL
if range.lowerBound > lastRangeEnd {
parts.append((String(text[lastRangeEnd..<range.lowerBound]), nil))
}
// Add the URL itself
parts.append((String(text[range]), url))
lastRangeEnd = range.upperBound
}
}
// Add any remaining text after the last URL
if lastRangeEnd < text.endIndex {
parts.append((String(text[lastRangeEnd..<text.endIndex]), nil))
}
return parts
}
}
extension View {
func clickableLinks(_ text: String) -> some View {
self.modifier(ClickableLinksModifier(text: text))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment