FormatStyle is the protocol behind every formatted() call: one format function from a
generic input to a generic output. Conform to it to package your own formatting logic as
reusable, testable values.
A Custom Style for a Standard Type
Wrap the configuration you keep repeating, then expose it as a static on FormatStyle so call
sites read like the built-ins.
struct ShortNumberFormat: FormatStyle {
let maxFractionLength: Int
func format(_ value: Double) -> String {
value.formatted(.number.precision(.fractionLength(0...maxFractionLength)))
}
}
extension FormatStyle where Self == ShortNumberFormat {
static func short(maxFractionLength: Int = 1) -> ShortNumberFormat {
.init(maxFractionLength: maxFractionLength)
}
}
Text(verbatim: 100.001.formatted(.short(maxFractionLength: 2)))
The output type is generic too: a style can return an AttributedString, for example bolding
the integer part with inlinePresentationIntent = .stronglyEmphasized, and Text renders it
directly.
Formatted on Your Own Types
Model types can adopt the same pattern. Add a formatted function constrained to styles whose
input is the type itself.
extension Product {
func formatted<Style: FormatStyle>(
_ style: Style
) -> Style.FormatOutput where Style.FormatInput == Self {
style.format(self)
}
}
struct ProductPriceFormat: FormatStyle {
func format(_ value: Product) -> AttributedString {
guard let oldPrice = value.oldPrice else {
return value.price.formatted(.number.attributed)
}
var string = AttributedString(
"\(value.price.formatted()) \(oldPrice.formatted())"
)
if let range = string.range(of: oldPrice.formatted()) {
string[range].inlinePresentationIntent = .strikethrough
}
return string
}
}
extension FormatStyle where Self == ProductPriceFormat {
static var price: ProductPriceFormat { .init() }
}
Text(product.formatted(.price))
A Lighter Nested Formatter
When one style per type feels heavy, a nested generic struct holding a closure gives the same call-site shape without a protocol conformance per use case.
extension Product {
struct Formatter<Output> {
let format: (Product) -> Output
}
func formatted<Output>(_ formatter: Formatter<Output>) -> Output {
formatter.format(self)
}
}
extension Product.Formatter where Output == AttributedString {
static var price: Self { .init { value in /* same logic */ } }
}