I'm struggling a bit with what the best way is to use custom string interpolators in a context that takes both a ExpressibleByStringInterpolation
or a StringProtocol
. As an example, let's imagine a SwiftUI Button
:
Button("50%") {}
Now let's imagine we have a custom string interpolator:
public extension String.StringInterpolation {
mutating func appendInterpolation(percent ratio: some BinaryFloatingPoint) {
self.appendLiteral(Self.percentFormatter.string(for: ratio) ?? "")
}
private static let percentFormatter = using(NumberFormatter()) {
$0.numberStyle = .percent
}
}
We could now ostensibly turn our Button
into:
Button("\(percent: rating)") {} // Extraneous argument label 'percent:' in call
Alas, we now get a bizarre error message (y'all, swiftc's error messages need a lot of love).
After a lot of research we discover that, in fact, the problem is unrelated to our interpolators or their argument labels, but rather, Button
has two competing initializers:
init(_ titleKey: LocalizedStringKey, action: @escaping () -> Void)
init<S>(_ title: S, action: @escaping () -> Void) where S : StringProtocol
Where String : StringProtocol
and LocalizedStringKey : ExpressibleByStringInterpolation
. The use of the interpolation appears to have shifted the compiler over to trying to use a different initializer than before, and failing to do so, since the interpolations available in that context do not support the arguments provided.
- Why did
swiftc
not continue to look for solutions to the requirements written in code; such as aString
(supportingStringProtocol
) which is alsoExpressibleByStringInterpolation
? - What is the recommended approach to disambiguating this use case or writing this in plain code which compiles and runs as expected?
Note, the following hint is enough to bop the compiler into, "OH, THAT'S WHAT YOU MEAN":
Button("\(percent: rating)" as String) {}