iosswiftxcodetextswiftui

SwiftUI tappable subtext


Is there any way in SwiftUI to open browser, when tapping on some part of the text.

I tried the above solution but it doesn't work because onTapGesture returns View which you cannot add to Text

Text("Some text ").foregroundColor(Color(UIColor.systemGray)) +
Text("clickable subtext")
   .foregroundColor(Color(UIColor.systemBlue))
   .onTapGesture {

   }

I want to have tappable subtext in the main text that's why using HStack will not work


Solution

  • 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)
            }
            
            
        }
    }
    

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

    import SwiftUI
    import UIKit
    
    struct 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)
        }
        
    }