When using CustomStringConvertible structs, regular String interpolation works fine, however because Text (SwiftUI) uses LocalizedString interpolation, it doesn’t, and requires you to either fully append .description, or to use Text(verbatim: ...) or Text(String(describing...))
Until(?) Apple harmonises the interpolation of String and LocalizedString, it seemed an easy enough fix to create an extension to make interpolation work on CustomStringConvertibles in an intuitive way. e.g.
extension LocalizedStringKey.StringInterpolation {
public mutating func appendInterpolation<S: CustomStringConvertible>(_ convertible: S) {
self.appendInterpolation(convertible.description)
}
}
This now means Text works as expected.
e.g.
struct Car: CustomStringConvertible {
var model: String
var description: String
}
var car: Car = Car(model: "Ford Ka", description: "Dubiously named oddity")
and in your views:
Text("\(car)")
outputs the description as above.
Great!
but … any other Text interpolations now produce an error : "Ambiguous use of appendInterpolation"
e.g.
let a = 1
Text("\(a)"). <--- error: Ambiguous use of appendInterpolation
Removing the extension above immediately resolves the error.
I would love to understand why, as I’ve been scratching my head on this for a while.
thanks!
>Solution :
The call is ambiguous between your overload of appendInterpolation, and this builtin overload, which takes a _FormatSpecifiable.
Since both methods are generic, Swift can’t decide which one is "better", and tells you that it is ambiguous.
You can make your method non-generic instead:
extension LocalizedStringKey.StringInterpolation {
public mutating func appendInterpolation(_ convertible: any CustomStringConvertible) {
self.appendInterpolation(convertible.description)
}
}
This causes Swift to always prefer your method, which might mess up some localised string keys. If you had Text("This is a number: \(someInt)"), Text will look for a localisation key of "This is a number: %lld". But if it calls your method, it doesn’t know to replace the interpolation with %lld, and from my experiments, it replaces it with %@ instead.
Just for the fun of it, let’s try conforming to _FormatSpecifiable! (Since it is prefixed with an underscore, this is non-stable API and will probably break)
extension Car: _FormatSpecifiable {
var _arg: String {
description
}
var _specifier: String {
"%@"
}
}
The _specifier property is presumably used to compute the actual localised string key. For example, if it were "I have a \(car)", and _specifier returns %@, the localisation key would be "I have a %@".
This also allows you to do weird things like this:
Text("\(car, specifier: "%p")") // shows a pointer to car
(This is calling this other overload)