Last active
September 15, 2024 14:39
-
-
Save natebird/6215668b95fb6dd728c2fe403f412fd7 to your computer and use it in GitHub Desktop.
Reusable view modifier to make URLs in a string clickable in SwiftUI
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
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