SwiftUI: Link phone numbers, emails, URLs in Text without Markdown
SwiftUI provides an easy way to link URLs and emails inside of a Text
view
using AttributedString
’s markdown initializer:
let text = "Jenny's at 867-5309."
+ " Email her at jenny@tutone.net."
+ " Find out more: https://en.wikipedia.org/wiki/867-5309/Jenny"
VStack {
Text(try! AttributedString(markdown: text))
}
However, this has a few shortcomings:
- the phone number isn’t linked/tappable
- we may not want all the markdown rendering: bullets (
*
) and brackets ([]
) in the text may end up rendering with unexpected results
We can use NSDataDetector
instead to help us construct our own
AttributedString
that can detect phone numbers and also prevent the user
from accidentally injecting markdown:
private let dataDetector: NSDataDetector = {
let types: NSTextCheckingResult.CheckingType = [.link, .phoneNumber]
return try! .init(types: types.rawValue)
}()
struct JennyView: View {
var body: some View {
VStack {
Text(attributedString(from: text)!)
}
}
private func attributedString(from text: String) -> AttributedString? {
var attributed = AttributedString(text)
let fullRange = NSMakeRange(0, text.count)
let matches = dataDetector.matches(in: text, options: [], range: fullRange)
guard !matches.isEmpty else { return nil }
for result in matches {
guard let range = Range<AttributedString.Index>(result.range, in: attributed) else {
continue
}
switch result.resultType {
case .phoneNumber:
guard
let phoneNumber = result.phoneNumber,
let url = URL(string: "sms://\(phoneNumber)")
else {
break
}
attributed[range].link = url
case .link:
guard let url = result.url else {
break
}
attributed[range].link = url
default:
break
}
}
return attributed
}
}
Here, I’ve used the sms://
scheme to open the Messaging app when the user
taps on a phone number. You could also use the tel://
scheme as well.
Now go forth, tap that phone number, and make Jenny your own.