SwiftUI tappable subtext SwiftUI tappable subtext xcode xcode

SwiftUI tappable subtext


Update for iOS 15 and higher:There is a new Markdown formatting support for Text, such as:

Text("Some text [clickable subtext](some url) *italic ending* ")

you may check WWDC session with a timecode for details

The old answer for iOS 13 and 14:

Unfortunately there is nothing that resembles NSAttributedString in SwiftUI. And you have only a few options. In this answer you can see how to use UIViewRepresentable for creating an old-school UILabel with click event, for example. But now the only SwiftUI way is to use HStack:

struct TappablePieceOfText: View {        var body: some View {                HStack(spacing: 0) {            Text("Go to ")                .foregroundColor(.gray)            Text("stack overflow")                .foregroundColor(.blue)                .underline()                .onTapGesture {                    let url = URL.init(string: "https://stackoverflow.com/")                    guard let stackOverflowURL = url, UIApplication.shared.canOpenURL(stackOverflowURL) else { return }                    UIApplication.shared.open(stackOverflowURL)                }                        Text(" and enjoy")                .foregroundColor(.gray)        }                    }}

UPDATEAdded solution with UITextView and UIViewRepresentable. I combined everything from added links and the result is quite good, I think:

import SwiftUIimport UIKitstruct TappablePieceOfText: View {        var body: some View {        TextLabelWithHyperlink()            .frame(width: 300, height: 110)    }    }struct TextLabelWithHyperlink: UIViewRepresentable {        func makeUIView(context: Context) -> UITextView {                let standartTextAttributes: [NSAttributedString.Key : Any] = [            NSAttributedString.Key.font: UIFont.systemFont(ofSize: 20),            NSAttributedString.Key.foregroundColor: UIColor.gray        ]                let attributedText = NSMutableAttributedString(string: "You can go to ")        attributedText.addAttributes(standartTextAttributes, range: attributedText.range) // check extention                let hyperlinkTextAttributes: [NSAttributedString.Key : Any] = [            NSAttributedString.Key.font: UIFont.systemFont(ofSize: 20),            NSAttributedString.Key.foregroundColor: UIColor.blue,            NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue,            NSAttributedString.Key.link: "https://stackoverflow.com"        ]                let textWithHyperlink = NSMutableAttributedString(string: "stack overflow site")        textWithHyperlink.addAttributes(hyperlinkTextAttributes, range: textWithHyperlink.range)        attributedText.append(textWithHyperlink)                let endOfAttrString = NSMutableAttributedString(string: " end enjoy it using old-school UITextView and UIViewRepresentable")        endOfAttrString.addAttributes(standartTextAttributes, range: endOfAttrString.range)        attributedText.append(endOfAttrString)                let textView = UITextView()        textView.attributedText = attributedText                textView.isEditable = false        textView.textAlignment = .center        textView.isSelectable = true                return textView    }    func updateUIView(_ uiView: UITextView, context: Context) {}    }

result of HStack and Text:HStack and Text

result of UIViewRepresentable and UITextView:

enter image description here

UPDATE 2:here is a NSMutableAttributedString little extension:

extension NSMutableAttributedString {        var range: NSRange {        NSRange(location: 0, length: self.length)    }    }


I didn't have the patience to make the UITextView and UIViewRepresentable work, so instead I made the whole paragraph tappable but still kept the underscored URL look/feel. Especially helpful if you are trying to add Terms of Service URL link to your app.

enter image description here

The code is fairly simple:

Button(action: {    let tosURL = URL.init(string: "https://www.google.com")! // add your link here    if UIApplication.shared.canOpenURL(tosURL) {        UIApplication.shared.open(tosURL)    }}, label: {    (Text("Store.ly helps you find storage units nearby. By continuing, you agree to our ")        + Text("Terms of Service.")            .underline()        )        .frame(maxWidth: .infinity, alignment: .leading)        .font(Font.system(size: 14, weight: .medium))        .foregroundColor(Color.black)        .fixedSize(horizontal: false, vertical: true)})    .padding([.horizontal], 20)


Base on Dhaval Bera's code, I put some struct.

struct TextLabelWithHyperLink: UIViewRepresentable {    @State var tintColor: UIColor    @State var hyperLinkItems: Set<HyperLinkItem>    private var _attributedString: NSMutableAttributedString    private var openLink: (HyperLinkItem) -> Void    init (    tintColor: UIColor,    string: String,    attributes: [NSAttributedString.Key : Any],    hyperLinkItems: Set<HyperLinkItem>,    openLink: @escaping (HyperLinkItem) -> Void  ) {    self.tintColor = tintColor    self.hyperLinkItems = hyperLinkItems    self._attributedString = NSMutableAttributedString(      string: string,      attributes: attributes    )    self.openLink = openLink  }      func makeUIView(context: Context) -> UITextView {    let textView = UITextView()    textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)    textView.isEditable = false    textView.isSelectable = true    textView.tintColor = self.tintColor    textView.delegate = context.coordinator    textView.isScrollEnabled = false    return textView  }    func updateUIView(_ uiView: UITextView, context: Context) {       for item in hyperLinkItems {      let subText = item.subText      let link = item.subText.replacingOccurrences(of: " ", with: "_")            _attributedString        .addAttribute(          .link,          value: String(format: "https://%@", link),          range: (_attributedString.string as NSString).range(of: subText)        )    }        uiView.attributedText = _attributedString  }    func makeCoordinator() -> Coordinator {    Coordinator(parent: self)  }    class Coordinator: NSObject, UITextViewDelegate {    var parent : TextLabelWithHyperLink        init( parent: TextLabelWithHyperLink ) {      self.parent = parent    }        func textView(      _ textView: UITextView,      shouldInteractWith URL: URL,      in characterRange: NSRange,      interaction: UITextItemInteraction    ) -> Bool {            let strPlain = URL.absoluteString        .replacingOccurrences(of: "https://", with: "")        .replacingOccurrences(of: "_", with: " ")            if let ret = parent.hyperLinkItems.first(where: { $0.subText == strPlain }) {        parent.openLink(ret)      }            return false    }  }}struct HyperLinkItem: Hashable {      let subText : String  let attributes : [NSAttributedString.Key : Any]?    init (    subText: String,    attributes: [NSAttributedString.Key : Any]? = nil  ) {    self.subText = subText    self.attributes = attributes  }    func hash(into hasher: inout Hasher) {    hasher.combine(subText)  }      static func == (lhs: HyperLinkItem, rhs: HyperLinkItem) -> Bool {    lhs.hashValue == rhs.hashValue  }}

Usage:

TextLabelWithHyperLink(  tintColor: .green,  string: "Please contact us by filling contact form. We will contact with you shortly.  Your request will be processed in accordance with the Terms of Use and Privacy Policy.",  attributes: [:],  hyperLinkItems: [    .init(subText: "processed"),    .init(subText: "Terms of Use"),  ],  openLink: {  (tappedItem) in    print("Tapped link: \(tappedItem.subText)")  })