We have previously solved similar issues like: SwiftUI tappable subtext
But how do you make subtext tappable, and also pop a dropdown at it's location when tapped? Do we need to use a webview? Split the strings with these timestamps and stitch them back?
Prefer a SwiftUI and AttributedString solution but is not required.
See below screenshot on how Apple solved this within Apple Podcast description, on tapping, the timestamp was highlighted, and a dropdown menu pops up:
You'd need to wrap a UITextView
for now.
menuConfigurationFor
delegate method to return the desired menu you want.primaryActionFor
so that it returns nil
. This causes the menu to show up on tap, instead of on long tap.Here is a simple example:
struct ContentView: View {
var body: some View {
TextViewWrapper(
text: try! .init(markdown: "Hello World! This is some text, with a [link](https://xyz/abc)!"),
actions: [
MenuAction(title: "Some Action") { url in
print("Do something with", url)
},
MenuAction(title: "Another Action") { url in
print("Do something else with", url)
},
]
)
}
}
struct MenuAction {
let title: String
let action: (URL) -> Void
}
struct TextViewWrapper: UIViewRepresentable {
let text: AttributedString
let actions: [MenuAction]
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.isEditable = false
textView.delegate = context.coordinator
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.attributedText = .init(text)
context.coordinator.actions = actions
}
func makeCoordinator() -> Coordinator {
.init()
}
class Coordinator: NSObject, UITextViewDelegate {
var actions = [MenuAction]()
func textView(_ textView: UITextView, primaryActionFor textItem: UITextItem, defaultAction: UIAction) -> UIAction? {
nil
}
func textView(_ textView: UITextView, menuConfigurationFor textItem: UITextItem, defaultMenu: UIMenu) -> UITextItem.MenuConfiguration? {
guard case let .link(url) = textItem.content else { return nil }
return .init(preview: nil, menu: UIMenu(children: actions.map { action in
UIAction(title: action.title) { _ in action.action(url) }
}))
}
}
}
You need to construct an AttributedString
with some links, and encode whatever information you need into the URLs. Then in menuConfigurationFor
, you can access the URL and create UIAction
s that do whatever you need to do.
In this example I only created a very simple MenuAction
struct to represent UIAction
s, but of course you can add a lot more properties to it to represent the whole range of things that a UIAction
can do, or even other subclasses of UIMenuElement
s.