How to create a multiline linkable Text in SwiftUI
iOS 14 and below
In iOS 14 and below, you’ll likely want to use a representable to accomplish this.
Here we wrap a UITextView
and leverage NSAttributedString
struct LinkTextView: UIViewRepresentable {
var attributedString: NSAttributedString
@Binding var height: CGFloat
var onLinkTap: ((URL) -> Void)?
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.isEditable = false
textView.isScrollEnabled = true
textView.delegate = context.coordinator
textView.backgroundColor = .clear
textView.dataDetectorTypes = [.link]
textView.attributedText = attributedString
// Other options as desired...
return textView
}
func updateUIView(_ textView: UITextView, context: Context) {
textView.attributedText = attributedString
updateHeight(for: textView)
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UITextViewDelegate {
var parent: LinkTextView
init(_ parent: LinkTextView) {
self.parent = parent
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
parent.onLinkTap?(URL)
return false // Return false to prevent the default action (opening the URL in a browser)
}
}
private func updateHeight(for textView: UITextView) {
let newSize = textView.sizeThatFits(CGSize(width: textView.frame.size.width, height: CGFloat.infinity))
if newSize.height != height {
// Avoid modifying state during update
Task { @MainActor in
self.height = newSize.height
}
}
}
}
// Usage
struct ContentView: View {
@State private var attributedTextHeight: CGFloat = .zero
private var attributedText: NSMutableAttributedString = {
let text = "Hello apple.com. This is some more text!"
let attributedString = NSMutableAttributedString(string: text)
if let linkRange = text.range(of: "apple.com.") {
attributedString.addAttribute(.link, value: "#", range: NSRange(linkRange, in: text))
}
// Apply other styles as needed...
return attributedString
}()
var body: View {
LinkTextView(attributedString: attributedText, height: $attributedTextHeight) { url in
// Handle link tap here...
}
.frame(height: attributedTextHeight)
}
}
iOS 15 and above
It’s recommended that you migrate away from using a representable, and use a more modern alternative
Markdown:
struct ContentView: View {
var body: some View {
Text("Hello [apple.com](https://www.apple.com/). This is some more text!")
}
}
Text itself accepts AttributedString
now
struct ContentView: View {
let markdownLink = try! AttributedString(markdown: "Hello [apple.com](https://www.apple.com/). This is some more text!")
var body: some View {
Text(markdownLink)
}
}