27

In Swift, as shown here, you can use "NSMutableAttributedString" to embed links in text.

How can I achieve this with SwiftUI?

I implemented it as follows, but that does not look how I want it to. this.

import SwiftUI

struct ContentView: View {
    var body: some View {
        HStack {
            Text("By tapping Done, you agree to the ")
            Button(action: {}) {
                Text("privacy policy")
            }
            Text(" and ")
            Button(action: {}) {
                Text("terms of service")
            }
            Text(" .")
        }
    }
}
mahan
  • 8,785
  • 1
  • 26
  • 60
Ika
  • 873
  • 1
  • 7
  • 17

8 Answers8

22

iOS 15+ (Swift 5.5 +)

SwiftUI has built-in support for rendering Markdown

To create a link, enclose the link text in brackets (e.g., [Duck Duck Go]) and then follow it immediately with the URL in parentheses (e.g., (https://duckduckgo.com)).

My favorite search engine is [Duck Duck Go](https://duckduckgo.com).

https://www.markdownguide.org/basic-syntax/#links

String variable

  1. Use init(_ value: String)

Creates a localized string key from the given string value.

let link = "[Duck Duck Go](https://duckduckgo.com)"
Text(.init(link))

Attributed text

  1. Use init(_ attributedContent: AttributedString)

Creates a text view that displays styled attributed content.

let markdownLink = try! AttributedString(markdown: "[Duck Duck Go](https://duckduckgo.com)")
Text(markdownLink)

Similar question: Making parts of text bold in SwiftUI


Use the Link View

A control for navigating to a URL.

https://developer.apple.com/documentation/swiftui/link

Link("Privacy Policy", destination: URL(string: "https://example.com")!)
mahan
  • 8,785
  • 1
  • 26
  • 60
  • fantastic, this really helped me for iOS 15! – cole Oct 26 '21 at 09:02
  • 3
    `let str = "[Privacy Policy](https://example.com)" Text(str) // does not work.` – Sylar Nov 08 '21 at 13:31
  • 1
    When using iOS 15+, using the [Link](https://developer.apple.com/documentation/swiftui/link) element would probably be safer (and nicer). Example: `Link("Privacy Policy", destination: URL(string: "https://example.com")!)` – Daniel Dec 13 '21 at 11:52
  • @Daniel Thanks. Agree and updated my answer. But they want to embede link in text. – mahan Dec 13 '21 at 13:36
  • 1
    @mahan The issue here is we can't change the foreground colour. It remains blue even after adding foregroundColor property to Text – Pritish Dec 31 '21 at 04:26
  • @Pritish try this for single text - Text(.init("[Link Example](https://www.google.es/)")) .accentColor(.red) – Shrikant Phadke Apr 06 '22 at 13:48
15

Just as @mahan mention, this works perfectly for iOS 15.0 and above by using markdown language:

Text("[Privacy Policy](https://example.com)")

But if you're using a String variable, and put it into the Text it wouldn't work. Example:

let privacyPolicyText = "Read our [Privacy Policy](https://example.com) here."
Text(privacyPolicyText) // Will not work

Solution for using a String variable

The reason is that Text got multiple initiations. So how do we solve this? The easiest way is just to do:

let privacyPolicyText = "Read our [Privacy Policy](https://example.com) here."
Text(.init(privacyPolicyText))

Result: Read our Privacy Policy here.

Jacob Ahlberg
  • 1,972
  • 5
  • 18
  • 40
7

Motjaba Hosseni is right so far there is nothing that resembles NSAttributedString in SwiftUI. This should solve your problem for the time being:

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Text("By tapping Done, you agree to the ")
            HStack(spacing: 0) {
                Button("privacy policy") {}
                Text(" and ")
                Button("terms of service") {}
                Text(".")
            }
        }
    }
}
jbnunn
  • 6,033
  • 4
  • 40
  • 64
LuLuGaGa
  • 10,500
  • 5
  • 38
  • 50
  • 4
    This does work as long as the first Text() isn't longer than a single line. – Kuhlemann Apr 05 '20 at 18:12
  • 9
    You can now use `Link("Stackoverflow", destination: URL(string: "https://stackoverflow.com")!)` in iOS 14 and Xcode 12. – Mike Gorski Mar 13 '21 at 15:45
  • 3
    Why is this the answer to the question? This does not address "how to use attributed string to embed links in text in swift ui" (paraphrasing). Meanwhile this post has been viewed over 7k times, and the answer is the comment above this one with 0 upvotes – elight May 13 '21 at 15:26
5

it's very simple just use LocalizedStringKey for example:

let message = "Hello, www.google.com this is just testing for hyperlinks, check this out our website https://www.apple.in thank you."

Text(LocalizedStringKey(message))
2

It's always an option to wrap a UIKit view in UIViewRepresentable. Just have to go through the manual process of exposing each attribute you want to change.

struct AttributedText: UIViewRepresentable {
    var attributedText: NSAttributedString

    init(_ attributedText: NSAttributedString) {
        self.attributedText = attributedText
    }

    func makeUIView(context: Context) -> UITextView {
        return UITextView()
    }

    func updateUIView(_ label: UITextView, context: Context) {
        label.attributedText = attributedText
    }
}

//usage: AttributedText(NSAttributedString())
Joe
  • 3,449
  • 22
  • 25
  • 2
    A big limitation of this approach seems to be that UIViewRepresentable does not communicate the intrinsicContentSize of the UITextView up to SwiftUI - AttributedText will take up all the space available to it, which plays havoc with StackViews etc – sam-w Oct 16 '20 at 04:56
1

I know it's a bit late but I solved the same problem using HTML. First I created a small helper and link model.

struct HTMLStringView: UIViewRepresentable {
  let htmlContent: String

  func makeUIView(context: Context) -> WKWebView {
    return WKWebView()
  }

  func updateUIView(_ uiView: WKWebView, context: Context) {
    uiView.loadHTMLString(htmlContent, baseURL: nil)
  }
}

struct TextLink {
    let url: URL
    let title: String
}

Next I created function that changes String to HTML and replaces first occurrence of @link to my tappable link.

var content = "My string with @link."
var link = TextLink(url: URL(string: "https://www.facebook.com")!, title: "Facebook")
var body: some View {
    let bodySize = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body).pointSize
    var html = "<span style=\"font: -apple-system-body; font-size:calc(\(bodySize)px + 1.0vw)\">"

    if let linkRange = content.range(of: "@link") {
        let startText = content[content.startIndex ..< linkRange.lowerBound]
        let endText = content[linkRange.upperBound ..< content.endIndex]
        html += startText
        html += "<a href=\"\(link.url.absoluteString)\">\(link.title)</a>"
        html += endText
    } else {
        html += content
    }
    
    html += "</span>"
    
    return HTMLStringView(htmlContent: html)
}
TheLegend27
  • 609
  • 6
  • 8
0

I tried concatenated Texts with Link in between and these are the ways for iOS 15+ and below iOS 15.

    if #available(iOS 15, *) {
        
        Text("[Seperate Link 1 ](https://www.android.com/intl/en_in/)")
            .font(.caption)
            .foregroundColor(Color.green)
        // green color is not applied.

        Text("[Seperate Link 2 ](https://www.apple.com/in/)")
            .font(.caption)
            .accentColor(Color.green)
        // green is applied.
        
        Text("By authorizing you agree
our ")
            .font(.caption)
            .foregroundColor(Color.black)
        + Text("[Terms and Conditions](https://www.android.com/intl/en_in/)")
            .font(.caption)
            .foregroundColor(Color.green) // default blue is applied
        + Text(" and ")
            .font(.caption)
            .foregroundColor(Color.black)
        + Text("[Privacy Policy](https://www.apple.com/in/)")
            .font(.caption)
            .foregroundColor(Color.green) // default blue
        // cannot use accentColor(Color.green) here
    }
    else{
        // lower iOS versions.
        VStack{
            Text("By authorizing you agree our ")
                .font(.caption)
                .foregroundColor(Color.black)
            
            HStack(spacing: 4 ) {
                Text("Terms and Conditions")
                    .font(.caption)
                    .foregroundColor(Color.green)
                    .onTapGesture {
                        let url = URL.init(string: "https://www.android.com/intl/en_in/")
                        guard let termsAndConditionURL = url, UIApplication.shared.canOpenURL(termsAndConditionURL) else { return }
                        UIApplication.shared.open(termsAndConditionURL)
                    }
                Text("and")
                    .font(.caption)
                    .foregroundColor(Color.black)
                Text("Privacy Policy")
                    .font(.caption)
                    .foregroundColor(Color.green)
                    .onTapGesture {
                        let url = URL.init(string: "https://www.apple.com/in/")
                        guard let privacyPolicyURL = url, UIApplication.shared.canOpenURL(privacyPolicyURL) else { return }
                        UIApplication.shared.open(privacyPolicyURL)
                    }
            }
            
        }
        
    }

Shrikant Phadke
  • 168
  • 1
  • 10
-5

Use built-in function +, it looks like a charm:

import SwiftUI

struct ContentView: View {
    var body: some View {
        HStack {
            Button(action: {

            }) {
                Text("By tapping Done, you agree to the ")
              + Text("privacy policy")
                  .foregroundColor(Color.blue)
              + Text(" and ")
              + Text("terms of service")
                  .foregroundColor(Color.blue)
              + Text(".")
            }
            .foregroundColor(Color.black)
        }
    }
}
Petr Syrov
  • 14,201
  • 3
  • 18
  • 30
  • 19
    This makes the entire text clickable, not just the link part. And in this case, there are 2 links and there is no way to differentiate which one was clicked. – Frankenstein Jul 18 '20 at 15:22