Using Mirror and extensions on ForEach and TupleView:
import SwiftUI
func getTaggedText<Tag: Hashable, Content: View>(@ViewBuilder content: () -> Content) -> [(Tag, String)] {
guard let gettable = content() as? GetTaggedText else {
return []
}
return gettable.getTaggedText()
}
print(getTaggedText {
Text("Zero").tag(0)
Text("One").tag(1)
Text("Two").tag(2)
} as [(Int, String)])
let values: [String] = ["Zero", "One", "Two"]
print(getTaggedText {
ForEach(values.indices, id: \.self) { index in
Text(values[index]).tag(index)
}
} as [(Int, String)])
//
// All the reflection code
//
protocol GetTaggedText {
func getTaggedText<Tag: Hashable>() -> [(Tag, String)]
}
// view: The modified Text(...).tag(...) view
func extractTaggedText<Tag: Hashable>(_ view: Any) -> (Tag, String)? {
let mirror = Mirror(reflecting: view)
// Get the tag trait
guard let tag = mirror.descendant("modifier", "value", "tagged") as? Tag else {
return nil
}
// Unwrap Text(...) from tag trait
guard let text = mirror.descendant("content") as? Text else {
return nil
}
let textMirror = Mirror(reflecting: text)
// get String from Storage -> String
if let string = textMirror.descendant("storage", "verbatim") as? String {
return (tag, string)
}
// get String from Stroage -> LocalizedStringKey
if let string = textMirror.descendant("storage", "anyTextStorage", "key", "key") as? String {
return (tag, string)
}
return (tag, "")
}
extension TupleView: GetTaggedText {
func getTaggedText<Tag: Hashable>() -> [(Tag, String)] {
guard let tuple = Mirror(reflecting: self).descendant("value") else {
return []
}
let values = Mirror(reflecting: tuple)
let count = values.children.count
var result: [(Tag, String)] = []
for index in 0..<count {
// Get individual View within tuple
guard let view = values.descendant(".\(index)") else {
break
}
guard let taggedText: (Tag, String) = extractTaggedText(view) else {
continue
}
result.append(taggedText)
}
return result
}
}
extension ForEach: GetTaggedText where Content: View {
func getTaggedText<Tag: Hashable>() -> [(Tag, String)] {
var result: [(Tag, String)] = []
for v in data {
let view = content(v)
guard let taggedText: (Tag, String) = extractTaggedText(view) else {
continue
}
result.append(taggedText)
}
return result
}
}
Results of prints:
[(0, "Zero"), (1, "One"), (2, "Two")]
[(0, "Zero"), (1, "One"), (2, "Two")]
(Sources that helped me figure this out: this post and this gist).