Skip to content

Custom Format Styles (Swift)

A SwiftUI guide for coding agents. Also covers custom formatstyle, formatted extension own type, attributed string formatting, strikethrough old price, reusable formatter swift, format model for display.

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 */ } }
}

Use this guidance in your coding agent

Install the Better Design MCP once. Your agent then loads this page with one call.

get-swiftui-guide({ topic: "swiftui-mastering-swift-foundation-formatter-api-custom-format-styles" })
claude mcp add --scope user better-design --transport http https://better-design.com/api/mcp --header "Authorization: Bearer <YOUR_API_KEY>"
Browse related design systems