Last active
September 30, 2025 22:04
-
-
Save davidbalbert/eef9c238531217a42b83d6903ef777dc to your computer and use it in GitHub Desktop.
Hack to extract a String from SwiftUI.Text
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Cursed! Do not use! | |
// Supports most, but not all Text initializers. | |
import SwiftUI | |
extension FormatStyle { | |
func format(any value: Any) -> FormatOutput? { | |
if let v = value as? FormatInput { | |
return format(v) | |
} | |
return nil | |
} | |
} | |
extension LocalizedStringKey { | |
var resolved: String? { | |
let mirror = Mirror(reflecting: self) | |
guard let key = mirror.descendant("key") as? String else { | |
return nil | |
} | |
guard let args = mirror.descendant("arguments") as? [Any] else { | |
return nil | |
} | |
let values = args.map { arg -> Any? in | |
let mirror = Mirror(reflecting: arg) | |
if let value = mirror.descendant("storage", "value", ".0") { | |
return value | |
} | |
guard let format = mirror.descendant("storage", "formatStyleValue", "format") as? any FormatStyle, | |
let input = mirror.descendant("storage", "formatStyleValue", "input") else { | |
return nil | |
} | |
return format.format(any: input) | |
} | |
let va = values.compactMap { arg -> CVarArg? in | |
switch arg { | |
case let i as Int: return i | |
case let i as Int64: return i | |
case let i as Int8: return i | |
case let i as Int16: return i | |
case let i as Int32: return i | |
case let u as UInt: return u | |
case let u as UInt64: return u | |
case let u as UInt8: return u | |
case let u as UInt16: return u | |
case let u as UInt32: return u | |
case let f as Float: return f | |
case let f as CGFloat: return f | |
case let d as Double: return d | |
case let o as NSObject: return o | |
default: return nil | |
} | |
} | |
if va.count != values.count { | |
return nil | |
} | |
return String.localizedStringWithFormat(key, va) | |
} | |
} | |
extension Text { | |
var string: String? { | |
let mirror = Mirror(reflecting: self) | |
if let s = mirror.descendant("storage", "verbatim") as? String { | |
return s | |
} else if let attrStr = mirror.descendant("storage", "anyTextStorage", "str") as? AttributedString { | |
return String(attrStr.characters) | |
} else if let key = mirror.descendant("storage", "anyTextStorage", "key") as? LocalizedStringKey { | |
return key.resolved | |
} else if let format = mirror.descendant("storage", "anyTextStorage", "storage", "format") as? any FormatStyle, | |
let input = mirror.descendant("storage", "anyTextStorage", "storage", "input") { | |
return format.format(any: input) as? String | |
} else if let formatter = mirror.descendant("storage", "anyTextStorage", "formatter") as? Formatter, | |
let object = mirror.descendant("storage", "anyTextStorage", "object") { | |
return formatter.string(for: object) | |
} | |
return nil | |
} | |
} | |
Text("Hello world!").string // => "Hello world!" | |
Text(AttributedString("Hello world!")).string // => "Hello world!" | |
Text("Hello \("world")!").string // => "Hello world!" | |
Text("Hello \(1 + 2)").string // => "Hello 3 | |
Text("Hello \(Date())").string // => "Hello Friday, January 19, 2024 at 11:25:59 Eastern Standard Time" | |
Text("Hello \(0.8, format: .percent)").string // => "Hello 80%" | |
Text(0.8, format: .percent).string // => "80%" | |
let formatter = DateFormatter() | |
formatter.dateStyle = .medium | |
formatter.timeStyle = .none | |
Text(Date(), formatter: formatter).string // => "Jan 19, 2024" | |
Text(NSDate(), formatter: formatter).string // => "Jan 19, 2024" |
Glad to hear it!
This is great thanks!
I was having issues getting it to work with Text interpolations though...
Adding this check after the first mirror.descendant("storage", "value", ".0")
check on line 29 seems get it working:
if let value = mirror.descendant("storage", "text", ".0") as? Text {
return value.string
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks! Help me a lot!