[英]SwiftUI tappable subtext
当点击文本的某些部分时,SwiftUI 中有什么方法可以打开浏览器。
我尝试了上述解决方案,但它不起作用,因为onTapGesture
返回您无法添加到Text
的View
Text("Some text ").foregroundColor(Color(UIColor.systemGray)) +
Text("clickable subtext")
.foregroundColor(Color(UIColor.systemBlue))
.onTapGesture {
}
我想在正文中有可点击的潜台词,这就是为什么使用HStack
不起作用
iOS 15 及更高版本的更新:对Text
有新的Markdown
格式支持,例如:
Text("Some text [clickable subtext](some url) *italic ending* ")
您可以查看带有时间码的 WWDC 会话以了解详细信息
iOS 13 和 14 的旧答案:
不幸的是,SwiftUI 中没有任何类似于 NSAttributedString 的东西。 你只有几个选择。 例如,在这个答案中,您可以看到如何使用UIViewRepresentable
创建带有 click event的老式UILabel
。 但现在唯一的 SwiftUI 方法是使用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)
}
}
}
更新添加了UITextView
和UIViewRepresentable
的解决方案。 我结合了添加链接的所有内容,结果非常好,我认为:
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) {}
}
UIViewRepresentable
和UITextView
的结果:
更新2:这是一个NSMutableAttributedString
小扩展:
extension NSMutableAttributedString {
var range: NSRange {
NSRange(location: 0, length: self.length)
}
}
我没有耐心让UITextView
和UIViewRepresentable
工作,因此我将整个段落设为可点击,但仍保留下划线的 URL 外观/感觉。 如果您尝试将服务条款 URL 链接添加到您的应用程序,则特别有用。
代码相当简单:
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)
基于 Dhaval Bera 的代码,我放了一些结构。
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
}
}
用法:
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)")
}
)
下面是我完整的 SwiftUI 解决方案。 使用以下解决方案,您放入的任何容器都将被很好地格式化,并且您可以使您想要点击的特定文本。
struct TermsAndPrivacyText: View {
@State private var sheet: TermsOrPrivacySheet? = nil
let string = "By signing up, you agree to XXXX's Terms & Conditions and Privacy Policy"
enum TermsOrPrivacySheet: Identifiable {
case terms, privacy
var id: Int {
hashValue
}
}
func showSheet(_ string: String) {
if ["Terms", "&", "Conditions"].contains(string) {
sheet = .terms
}
else if ["Privacy", "Policy"].contains(string) {
sheet = .privacy
}
}
func fontWeight(_ string: String) -> Font.Weight {
["Terms", "&", "Conditions", "Privacy", "Policy"].contains(string) ? .medium : .light
}
private func createText(maxWidth: CGFloat) -> some View {
var width = CGFloat.zero
var height = CGFloat.zero
let stringArray = string.components(separatedBy: " ")
return
ZStack(alignment: .topLeading) {
ForEach(stringArray, id: \.self) { string in
Text(string + " ")
.font(Theme.Fonts.ofSize(14))
.fontWeight(fontWeight(string))
.onTapGesture { showSheet(string) }
.alignmentGuide(.leading, computeValue: { dimension in
if (abs(width - dimension.width) > maxWidth) {
width = 0
height -= dimension.height
}
let result = width
if string == stringArray.last {
width = 0
}
else {
width -= dimension.width
}
return result
})
.alignmentGuide(.top, computeValue: { dimension in
let result = height
if string == stringArray.last { height = 0 }
return result
})
}
}
.frame(maxWidth: .infinity, alignment: .topLeading)
}
var body: some View {
GeometryReader { geo in
ZStack {
createText(maxWidth: geo.size.width)
}
}
.frame(maxWidth: .infinity)
.sheet(item: $sheet) { item in
switch item {
case .terms:
TermsAndConditions()
case .privacy:
PrivacyPolicy()
}
}
}
}
我使用了@АлександрГрабовский 的答案,但我还必须进行一些配置以使其适合我。 我的文本字段中有 2 个链接,它们都有自定义颜色并将用户引导到不同的页面。 我也不希望启用滚动,但如果我禁用它,高度不会得到调整,它会延伸到视图的外部。 我尝试了很多不同的东西,目前我发现了一个适合我的解决方案,所以我想我不妨在这里分享一下。
再次感谢@АлександрГрабовский 的回答,我设法做到了。 我必须做的唯一调整是:
将与文本颜色相关的链接属性设置为另一个 var,并将 UITextView 上的“linkTextAttributes”属性设置为该属性,以更改文本颜色,而字体和链接目标我使用了他的回复中建议的内容。 如果我将颜色属性设置为链接本身,文本颜色不会改变。
让 linkAttributes: [NSAttributedString.Key : Any] = [ NSAttributedString.Key.foregroundColor: UIColor(named: "my_custom_green") ?? UIColor.blue ] textView.linkTextAttributes = linkAttributes
我不希望 UITextView 滚动,我发现保持多行高度而不滚动的唯一方法(将 isScrollEnabled 设置为 false 对我不起作用)是将 scrollRangeToVisible 设置为我拥有的最后一个字符串范围。
textView.scrollRangeToVisible(ppWithHyperlink.range)
我不知道这是否是最好的选择,但这是我发现的……希望将来在 swiftUI 中有更好的方法来做到这一点!!!
使用UITextView
的可点击字符串
struct TextLabelWithHyperlink: UIViewRepresentable {
@State var tintColor: UIColor = UIColor.black
@State var arrTapableString: [String] = []
var configuration = { (view: UITextView) in }
var openlink = {(strtext: String) in}
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) {
configuration(uiView)
let stringarr = NSMutableAttributedString(attributedString: uiView.attributedText)
for strlink in arrTapableString{
let link = strlink.replacingOccurrences(of: " ", with: "_")
stringarr.addAttribute(.link, value: String(format: "https://%@", link), range: (stringarr.string as NSString).range(of: strlink))
}
uiView.attributedText = stringarr
}
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 (self.parent.arrTapableString.contains(strPlain)) {
self.parent.openlink(strPlain)
}
return false
}
}}
在 swiftui 中实现
TextLabelWithHyperlink(arrTapableString: ["Terms of Use", "Privacy Policy"]) { (textView) in
let 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."
let attrib = NSMutableAttributedString(string: string, attributes: [.font: UIFont(name: Poodlife_Font.oxygen_regular, size: 14)!,.foregroundColor: UIColor.black])
attrib.addAttributes([.font: UIFont(name: Font.oxygen_bold, size: 14)!,
.foregroundColor: UIColor.black], range: (string as NSString).range(of: "Terms of Use"))
attrib.addAttributes([.font: UIFont(name: Font.oxygen_bold, size: 14)!,
.foregroundColor: UIColor.black,
.link: "Privacy_Policy"], range: (string as NSString).range(of: "Privacy Policy"))
textView.attributedText = attrib
} openlink: { (tappedString) in
print("Tapped link:\(tappedString)")
}
在 iOS 15 你可以试试
Text("Apple website: [click here](https://apple.com)")
从 iOS 15 开始,您可以将AttributedString
和Markdown
与 Text 一起使用。
使用Markdown
的示例:
Text("Plain text. [This is a tappable link](https://stackoverflow.com)")
AttributedString
使您可以更好地控制格式。 例如,您可以更改链接颜色:
var string = AttributedString("Plain text. ")
var tappableText = AttributedString("I am tappable!")
tappableText.link = URL(string: "https://stackoverflow.com")
tappableText.foregroundColor = .green
string.append(tappableText)
Text(string)
这是它的样子:
附注:如果您希望您的可点击文本具有与在浏览器中打开 URL 不同的行为,您可以为您的应用定义自定义 URL 方案。 然后,您将能够使用onOpenURL(perform:)
处理链接上的点击事件,该事件注册处理程序以在视图接收到视图所在的场景或窗口的 url 时调用。
对于 iOS 14
我使用了像Down这样的第三方库。 这比创建自己的解析引擎要简单得多。
import SwiftUI
import Down
struct ContentView: View {
@State var source = NSAttributedString()
var body: some View {
VStack {
TextView(attributedText: source)
.padding(.horizontal)
.padding(.vertical, 10)
.frame(maxWidth: .infinity, minHeight: 64, maxHeight: 80, alignment: .leading)
.background(Color( red: 236/255, green: 236/255, blue: 248/255))
.cornerRadius(10)
.padding()
}
.onAppear {
let down = Down(markdownString: "Work hard to get what you like, otherwise you'll be forced to just like what you get! [tap here](https://apple.com)")
source = try! down.toAttributedString(.default, stylesheet: "* {font-family: 'Avenir Black'; font-size: 15}")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct TextView: UIViewRepresentable {
var attributedText: NSAttributedString
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.autocapitalizationType = .sentences
textView.isSelectable = true
textView.isEditable = false
textView.backgroundColor = .clear
textView.isUserInteractionEnabled = true
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.attributedText = attributedText
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.