如何在SwiftUI中创建超链接

56

在 Swift 中,可以使用 NSMutableAttributedString 在文本中嵌入链接,例如此处所示的链接

那么,在 SwiftUI 中要如何实现相同的效果呢?

我尝试了以下代码,但它并不符合我的期望: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(" .")
        }
    }
}

SwiftUI目前还不支持任何“Attribute”。 - Mojtaba Hosseini
13个回答

83

iOS 15+(Swift 5.5 +)

SwiftUI内置支持渲染Markdown。

要创建链接,请用方括号括起链接的文本(例如,[Duck Duck Go]),然后紧接着在括号中加上URL(例如,(https://duckduckgo.com))。

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

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

字符串变量

  1. 使用 init(_ value: String)

从给定的字符串值创建一个本地化字符串键。

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

字符串插值

  1. 使用 init(_ value: String)

从给定的字符串值创建一个本地化字符串键。

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

属性文本

  1. 使用 init(_ attributedContent: AttributedString)

创建一个显示样式化属性内容的文本视图。

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

类似的问题: 在SwiftUI中使文本部分加粗


使用链接视图

用于导航到URL的控件。

Link("Privacy Policy", destination: URL(string: "https://example.com")!)

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


太棒了,这真的对我在iOS 15上有所帮助! - cole
5
让字符串变量 str = "隐私政策" Text(str) //无法工作。 - Sylar
1
在使用iOS 15+时,使用Link元素可能会更加安全(而且更加美观)。例如:Link("隐私政策", destination: URL(string: "https://example.com")!) - Daniel
1
@mahan 这里的问题是我们无法更改前景色。即使在Text中添加了foregroundColor属性,它仍然保持蓝色。 - BraveEvidence
1
你可能需要使用.init来包含插值的字符串字面量,例如 Text(.init("[链接文本](\(url))")) - Lauren Yim
显示剩余3条评论

27

正如@mahan所提到的,使用Markdown语言,此方法适用于iOS 15.0及以上版本:

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

但是,如果您正在使用一个String变量,并将其放入Text中,它将不起作用。例如:

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

使用字符串变量的解决方案

原因是被多次初始化。那么我们该如何解决呢?最简单的方法就是:

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

结果:请阅读我们的隐私政策


Link("隐私政策", destination: URL(string: "https://example.com")!) @jacobAhlberg - BraveEvidence
1
@Pritish 这个方法可行,但它是另一个组件。如果你想在Text组件中间有超链接,这个方法就不行了。例如:"如果你想阅读更多,请点击[这里]({Your Link})"。 - Jacob Ahlberg
@JacobAhlberg 有没有办法改变链接的颜色? - esp
2
@esp 只需使用名为 .accentColorviewModifier - Jacob Ahlberg
我建议:let privacyPolicyText = AttributedString("在此处阅读我们的[隐私政策](https://example.com)") - Jayden Irwin
显示剩余2条评论

20

很简单,只需使用 LocalizedStringKey 例如:

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

4
您知道您可以编辑帖子吗?我问这个问题是因为看起来您删除了一个类似的帖子,然后创建了这个帖子。 - Yunnosch
这就是我一直在寻找的!谢谢。 - Heyman

7
为了保险起见,如果您的文本不是字符串文字,您可能希望使用.init。换句话说,如果有任何字符串连接、插值等操作,您可能希望使用Text(.init(...))
请注意,这种情况下的.init实际上指的是LocalizedStringKey.init,因此仍将进行本地化,就像当您只传递一个字符串字面量时一样。
以下是一些示例及其在Xcode 14预览中的渲染输出。
let foo = "Foo"
let bar = "Bar"
let link = "link"

Group {
  Text("Foo [Bar](link) Baz") // ✅
  Text("Foo" + " [Bar](link) Baz") // ❌
  Text(foo + " [Bar](link) Baz") // ❌
  Text("\(foo) [Bar](link) Baz") // ✅
  Text("\(foo) [Bar](\(link)) Baz") // ❌
  Text("\(foo) [\(bar)](\(link)) Baz") // ❌
}

Rectangle().height(1)

Group {
  Text(.init("Foo [Bar](link) Baz")) // ✅
  Text(.init("Foo" + " [Bar](link) Baz")) // ✅
  Text(.init(foo + " [Bar](link) Baz")) // ✅
  Text(.init("\(foo) [Bar](link) Baz")) // ✅
  Text(.init("\(foo) [Bar](\(link)) Baz")) // ✅
  Text(.init("\(foo) [\(bar)](\(link)) Baz")) // ✅
}

Rendered output


这些链接可点击吗? - Teja Nandamuri
@TejaNandamuri 这已经有一段时间了,但我希望它们是可点击的!如果不是,请告诉我们 :) - damd
不,它们默认情况下不是这样的,我们必须向这些文本或组添加openURL修饰符,以允许打开URL。 - Teja Nandamuri

4
我尝试将文本和链接连接起来,以下是适用于iOS 15及以上版本和低于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)
                    }
            }
            
        }
        
    }

"

" 可以翻译为 "

{{链接1:Swift}} {{链接2:iOS}} {{链接3:SwiftUI}}

"。

3

Motjaba Hosseni 目前正确,SwiftUI 中没有类似于NSAttributedString的东西。 暂时可以通过以下方法解决您的问题:

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(".")
            }
        }
    }
}

5
只要第一个Text()不超过一行,这个方法就可以运作。 - Kuhlemann
11
在 iOS 14 和 Xcode 12 中,你现在可以使用 Link("Stackoverflow", destination: URL(string: "https://stackoverflow.com")!)。该代码可以创建一个超链接到 Stack Overflow 网站。 - Mike Gorski
4
为什么这是问题的答案?这并没有解决“如何在Swift UI中使用属性字符串嵌入链接”的问题(改述)。与此同时,这篇帖子已经被查看了7,000多次,而答案是上面那条评论,却没有得到赞同。 - elight

2

你可以选择将UIKit视图包装在UIViewRepresentable中。只需通过手动过程暴露要更改的每个属性即可。

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())

2
这种方法的一个很大限制似乎是UIViewRepresentable无法将UITextView的intrinsicContentSize传达给SwiftUI - AttributedText将占用其可用的所有空间,这会对StackViews等造成影响。 - sam-w

2
我知道有点晚了,但是我使用HTML解决了同样的问题。 首先,我创建了一个小的helper和link模型。
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
}

接下来,我创建了一个将字符串转换为HTML并将第一个@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)
}

1
你也可以试试这种方法。我觉得这是最简单的解决方案。
import SwiftUI

struct ContentView: View {
  var body: some View {
    VStack {
      Text(getAttriText())
    }
    .environment(\.openURL, OpenURLAction(handler: { url in
      if url.absoluteString.contains("privacy") {
        // action
      }
      
      if url.absoluteString.contains("terms") {
        // action
      }
      return .systemAction // change if you want to discard action
    }))
  }
  
  func getAttriText() -> AttributedString {
    
    var attriString = AttributedString("By tapping Done, you agree to the privacy policy and terms of service")
    attriString.foregroundColor = .black
    
    if let privacyRange = attriString.range(of: "privacy policy") {
      attriString[privacyRange].link = URL(string: "www.apple.com/privacy")
      attriString[privacyRange].underlineStyle = .single
      attriString[privacyRange].foregroundColor = .blue
    }
    
    if let termsRange = attriString.range(of: "terms of service") {
      attriString[termsRange].link = URL(string: "www.apple.com/terms")
      attriString[termsRange].underlineStyle = .single
      attriString[termsRange].foregroundColor = .blue
    }
    
    return attriString
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}

0

*** iOS 15 ***

您也可以像这样添加电子邮件或电话号码:

    var infoText = "For any queries reach out to [email] or call [phone]"
    var phone = "+45 12345678".replacingOccurrences(of: " ", with: "")
    var email = "some-email@some-host.something"
    
    var footerText: String {
        return infoText
            .replacingOccurrences(
                of: "[email]",
                with: "[\(email)](mailto:\(email))"
            )
            .replacingOccurrences(
                of: "[phone]",
                with: "[\(phone)](tel:\(phone))"
            )
    }

Text(.init(footerText))

请记住链接(主要是电话号码,因为电子邮件没有空格)不能有空格。

还要记住这在模拟器上不起作用。您需要一个真实的设备来测试它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接