Fucking Format Style!
GitHub Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Format Styles In Excruciating Detail

Swift’s FormatStyle and ParseableFormatStyle are the easiest way to convert Foundation data types to and from localized strings. Unfortunately Apple hasn’t done a great job in documenting just what it can do, or how to use them.

This site is going to help you do just that.

Need a version of the site without all of the cussing in the title? Go to goshdarnformatstyle.com


How do I even know where to start?

You can start off by reading The Basics.

If you have a specific need and aren’t quite sure how to execute it, follow along:

I have a

Xcode 13+

All built-in number types are supported by these format styles (Float, Double, Int, and Decimal)

Xcode 13+

See Number Style

Property Description

Rounding

Customize the rounding behaviour

Sign

Do you want to show or hide the + or - sign?

Decimal Separator

Do you want to show or hide the decimal separator

Grouping

How do you want the thousands numbers to be grouped

Precision

How many fractional or significant digits do you want to show

Notation

Enable scientific or compact notation

Scale

Scale the number up or down before display

Locale

Set the Locale for one output

Compositing

Mix and match any and all of the above

AttributedString output

Output an AttributedString

Xcode 13+

See Percent Style

Xcode 13+

When formatting a floating point number (Double/Float/Decimal), 1.0 is “100%”, while formatting the integer 1 is only “1%”

Property Description

Rounding

Customize the rounding behaviour

Sign

Do you want to show or hide the + or - sign?

Decimal Separator

Do you want to show or hide the decimal separator

Grouping

How do you want the thousands numbers to be grouped

Precision

How many fractional or significant digits do you want to show

Notation

Enable scientific or compact notation

Scale

Scale the number up or down before display

Locale

Set the Locale for one output

Compositing

Mix and match any and all of the above

AttributedString output

Output an AttributedString

The various percent formatters handle integers and floating point values differently.

  • Int(1) outputs “1%”
  • Int(100) outputs “100%”
  • Double(1.0) outputs “100%”
  • Double(0.01) outputs “1%”

Xcode 13+

See Currency Style

Xcode 13+

Currency requires you to set the ISO 4217 code for the target currency to output.

See all ISO 4217 codes

Floating point types shouldn’t be used to store or do calculations on numbers that require a lot of accuracy. This includes currency values.

Property Description

Rounding

Customize the rounding behaviour

Sign

Do you want to show or hide the + or - sign?

Decimal Separator

Do you want to show or hide the decimal separator

Grouping

How do you want the thousands numbers to be grouped

Precision

How many fractional or significant digits do you want to show

Presentation

Controls the style of the displayed currency

Scale

Scale the number up or down before display

Locale

Set the Locale for one output

Compositing

Mix and match any and all of the above

AttributedString output

Output an AttributedString

Xcode 13+ Xcode 14+

Xcode 13+

Apple added a date extension to Date that lets you output the date and/or time in certain formats.

See Date and Time Style

Xcode 13+

The dateTime() format style lets you compose a date string by choosing which date components you’d like to include. Each component can be further customized passing in some options.

See compositing using .dateTime

Xcode 13+

Output date strings that conform to the ISO 8601 standard.

See ISO8601 Style

Xcode 13+

Relative date formatting outputs a plain language string that describes how far away that date is to right now. You can also customize which date components are used in the output.

This is similar to the components format style on date ranges, but that one uses date ranges instead of assuming the current date and time.

See Relative Style

Xcode 13+ Xcode 14+

The Verbatim Format Style is how you create a date string with any and all extra characters you might want mixed in. This is a replacement for the dateFormat strings on (NS)DateFormatter eg. “yyyy-MMM-dd”.

See Verbatim Style

Xcode 13+

The interval formatter just shows the earliest and latest dates in a range.

See Interval Style

Xcode 13+

Use the components format style if you want a plain language representation of the distance between the earliest and latest dates in a date range. This is similar to the relative date format style (but for date ranges).

See Components Style

Xcode 13+

The list format style lets you take an array of data objects, and output them as a list on screen. You can also customize how each object is formatted within the list.

See List style

Xcode 13+ Xcode 14+

Any unit that’s supported by the Measurement API can be formatted with many different customization options. Updated in Xcode 14.

See Measurement Style

Xcode 13+

A person’s name is tricky to localize correctly, the PersonNameComponents format style can handle the complexities of localization for you.

See Person Name Components

Xcode 13+

Easily display how many gigabytes that byte count is.

See Byte Count Style

Xcode 14+

Easily format the Duration type.

See Duration Style

Xcode 14+

Shows the Duration represented in a combination of hours, minutes, and seconds.

See Duration Style

Xcode 14+

Shows the Duration using a specific set of units.

See Duration Style

Xcode 14+

Format your Universal Resource Locator.

See URL Style

Xcode 13+

You can easily bend the FormatStyle protocol to your will and arbitrarily convert any type into any type.

See Custom FormatStyle (with locale and attributed string support)

Several of the included format styles also conform to ParseableFormatStyle, a protocol built to parse strings into their respective data types.

You can read about it in more detail here.

Xcode 13+

By setting up either a Date.FormatStyle or Date.ISO8601FormatStyle with your date structure, you can parse dates easily.

See Parsing Dates

See Parsing ISO8601 Dates

Xcode 13+

All of Swift’s built-in numeric types can be parsed from strings. You can also handle parsing percentages and currencies.

See Parsing Numbers

See Parsing Percentages

See Parsing Currencies

Xcode 13+

Generally useful for parsing names from string respecting a Locale, it’s easy to parse names.

See Parsing Names

Xcode 14+

Parse a URL string into a URL value.

See Parsing URLs


FAQ

For Swift, it’s the easiest way to convert a data type to a fully localized display string.

Introduced with iOS 15, the FormatStyle is a Swift protocol that outlines a way to take one format and convert it to another.

On top of this, Apple built out a suite of format styles to easily convert the built in data types to strings for display with a dizzying amount of customization.

This replaces the old Formatter subclasses that were ported over from Objective-C and Apple themselves now recommends that new Swift codes uses this instead.

Apple’s Data Formatting documentation

FormatStyle is only available when you’re writing Swift. If you’re writing Objective-C, you’ll need to use the old Formatter classes.
The only catch is that Apple has done a bad job of documenting them. Unless you know what it can do, there’s no way to discover how to do it and you’ll end up trying to re-invent the wheel.

At the core of it:

  1. Performance
  2. Simplicity

Performance

Creating an instance of a Formatter subclass is expensive and Apple’s documentation doesn’t go out of it’s way to tell you that. An easy trap to fall into is re-initializing a formatter every time you’d like to display some data as a string.

This isn’t too bad if you’re re-creating a formatter once per UIView, but if you make the mistake of creating a new formatter for every cell in a UICollectionView, you’re going to be in pain.

FormatStyle works like you expect it, and it handles everything behind the scenes.

Kahn Winter did a bunch of performance testing and found that FormatStyle use is slightly more performant than correctly re-using Formatter subclasses correctly.

So the main benefit to their use is…

Simplicity

Let’s say you want to display an Double as a percentage, you have to:

  1. You create an instance of your NumberFormatter somewhere that’s reusable.
  2. Customize the properties of the formatter to output the exact format you’d like to use.
  3. Call the formatter’s method to output your string.

“That’s not that bad” you may think to yourself, but remember that that shiny new formatter only outputs that exact output. If you have a new output need, you have to create a new formatter.

Using FormatStyles work like you expect them, every piece of data can have their output customized and can be set up for reuse easily.

Check the sidebar/menu, you can format (and parse!) any of these types out of the box.

You can even write your own formatters from the ground up and support your own custom data types.

It’s strings by default. Some of the built-in format styles will also output attributed strings.

If you’re feeling really clever, you can create a custom format style and output whatever you want.


Minimum Requirements

Xcode 13+

This badge represents a style that is available on any platform that is built by Xcode 13 and above (iOS 15.0+, iPadOS 15.0+, Mac Catalyst 15.0+, tvOS 15.0+, watchOS 8.0+, macOS 12.0+):

  • Number styles (including currency and percent styles)
  • Date styles (including iso8601, relative, and verbatim styles)
  • Date range styles (interval, and components)
  • Measurement styles
  • List styles
  • Person name styles
  • Byte count styles

Xcode 14+

This badge represents a style that has been updated, or is only available only, on platforms built by Xcode 14 and above (iOS 16.0+, iPadOS 16.0+, Mac Catalyst 16.0+, tvOS 16.0+, watchOS 9.0+, macOS 13.0+):

  • (Updated) Byte count style
  • (Updated) Measurement style
  • Duration style
  • URL style
  • (Updated) Verbatim date style

The Basics

You can access this new system in a few ways:

  1. Call .formatted() on a data type for a sensible, localized default
  2. Call .formatted(_: FormatStyle) on a data type, and pass in a pre-defined or custom FormatStyle to customize your output
  3. Call .format() on an instance of a FormatStyle

At its most basic, calling .formatted() will give you a sensible default that uses your device’s current locale and calendar to display the value.

// Dates
Date(timeIntervalSinceReferenceDate: 0).formatted() // "12/31/2000, 5:00 PM"

// Measurements
Measurement(value: 20, unit: UnitDuration.minutes).formatted()     // "20 min"
Measurement(value: 300, unit: UnitLength.miles).formatted()        // "300 mi"
Measurement(value: 10, unit: UnitMass.kilograms).formatted()       // "22 lb"
Measurement(value: 100, unit: UnitTemperature.celsius).formatted() // "212°F"

// Numbers
32.formatted()               // "32"
Decimal(20.0).formatted()    // "20"
Float(10.0).formatted()      // "10"
Int(2).formatted()           // "2"
Double(100.0003).formatted() // "100.0003"

// Names
PersonNameComponents(givenName: "Johnny", familyName: "Appleseed").formatted() // "Johnny Appleseed"

// Lists
["Alba", "Bruce", "Carol", "Billson"].formatted() // "Alba, Bruce, Carol, and Billson"

// TimeInterval
let referenceDay = Date(timeIntervalSinceReferenceDate: 0)
(referenceDay ..< referenceDay.addingTimeInterval(200)).formatted() // "12/31/00, 5:00 – 5:03 PM"

// Calling format on a style
let byteCountStyle = ByteCountFormatStyle(
    style: .file,
    allowedUnits: .all,
    spellsOutZero: true,
    includesActualByteCount: true,
    locale: Locale.current
)

byteCountStyle.format(1_000_000_000) //"1 GB (1,000,000,000 bytes)"

In general, these are useful to quickly convert your values into strings.