How to Format Strings Using Format Specifiers in Swift

How to Format Strings Using Format Specifiers in Swift

Written by

in

Quick answer: To format a string with specifiers in Swift, import Foundation and call String(format: "...", arguments). Use tokens like %d or %ld for integers, %.2f for fixed-precision floating-point numbers, %02d for leading zero padding, and %@ for bridged objects or Swift strings.

Why this happens

Developers coming from Objective-C frequently rely on [NSString stringWithFormat:] to control numerical precision, apply zero padding, or format hex representations. In Swift, basic string creation uses string interpolation like "\(value)". However, basic interpolation does not provide built-in specifiers to control decimal places, leading zeros, or byte alignments without additional formatting logic.

Swift strings are backed by the standard library’s String type, but advanced formatting capabilities require Apple’s Foundation framework. Calling String(format:_:) bridges Swift types into C-style variadic arguments (CVarArg). If an incorrect format specifier is passed—such as matching a 64-bit Int with %d on a platform where size mismatches occur, or providing a Swift struct where an object token (%@) is expected—the output will produce garbage data, compiler warnings, or runtime faults.

Modern Swift versions offer two main approaches: the traditional String(format:) API inherited from NSString, and the newer, type-safe FormatStyle APIs introduced in iOS 15 and macOS 12 (.formatted()). Understanding how and when to use each approach avoids silent formatting bugs and localization errors.

How to fix it

1. Use Foundation’s String(format:) Initializer

Import Foundation to access String(format:_:). This method accepts standard C printf format specifiers and variable arguments conforming to CVarArg.

import Foundation

let count: Int = 42
let ratio: Double = 3.1415926
let title: String = "Item"

// Format with padding and precision
let formatted = String(format: "%@ #%03d: ratio is %.2f", title, count, ratio)
print(formatted) 
// Output: Item #042: ratio is 3.14

Common format specifiers in Swift include:

  • %d or %ld: Signed integers (Int). When targeting 64-bit devices, Int matches %ld, but String(format:) handles standard Swift Int with %d or %ld via automatic bridging.
  • %.Nf: Floating-point numbers (Double, Float) rounded to N decimal places.
  • %0Nd: Integer padded with leading zeros up to N characters wide.
  • %x or %X: Lowercase or uppercase hexadecimal representation.
  • %@: Objective-C object or Swift string bridged via NSString.
  • %%: Escaped literal percent sign.

2. Format Hexadecimal and Memory Values

To format numbers as hexadecimals or display memory pointers, pass integer types directly into %x, %X, or %p.

import Foundation

let byteValue: UInt8 = 255
let hexString = String(format: "0x%02X", byteValue)
print(hexString) 
// Output: 0x00FF

let timestamp = Int(Date().timeIntervalSince1970)
let hexTime = String(format: "Timestamp in hex: %08x", timestamp)
print(hexTime)
// Output: Timestamp in hex: 672cf398 (example)

Prefixing %X with 02 or 08 ensures fixed-width representations, which is essential when printing raw byte buffers, color hex codes, or network packet data.

3. Use Positional Arguments for Reordering and Localization

When formatting strings that will be translated into multiple languages, word orders change. Using numbered positional arguments (%1$@, %2$d) prevents format order dependencies.

import Foundation

let username = "Sarah"
let unreadCount = 5

// English format
let formatEN = "%1$@ has %2$d unread messages."
let resultEN = String(format: formatEN, username, unreadCount)
print(resultEN)
// Output: Sarah has 5 unread messages.

// Alternative language where the count appears before the name
let formatAlt = "%2$d messages pending for %1$@."
let resultAlt = String(format: formatAlt, username, unreadCount)
print(resultAlt)
// Output: 5 messages pending for Sarah.

Positional tokens ensure that changing the order of arguments in localized .strings files does not crash the application or scramble parameter mapping at runtime.

4. Use Modern Swift FormatStyle for Numbers and Dates

For most modern Swift code targeting iOS 15, macOS 12, or later, Apple recommends the type-safe FormatStyle protocol over String(format:). It avoids printf format specifier errors entirely and adheres to system locale settings.

import Foundation

// Double precision control
let value: Double = 1234.5678
let formattedValue = value.formatted(.number.precision(.fractionLength(2)))
print(formattedValue)
// Output: 1,234.57 (in en_US locale)

// Currency
let price: Double = 49.99
let formattedPrice = price.formatted(.currency(code: "USD"))
print(formattedPrice)
// Output: $49.99

// Percent
let progress: Double = 0.854
let formattedPercent = progress.formatted(.percent.precision(.fractionLength(1)))
print(formattedPercent)
// Output: 85.4%

Using FormatStyle ensures numbers automatically use correct localized decimal points (such as , vs .) and grouping separators without manual configuration.

How to prevent it

  • Import Foundation: String(format:_:) is part of Foundation. Calling it in pure Swift files without import Foundation causes compilation errors.
  • Avoid %@ on arbitrary Swift structs: The %@ token expects an object pointer or an NSString. Passing custom Swift structs or enums that do not bridge to NSObject will print internal description wrappers or cause undefined behavior. Convert them to String first or use "\(value)".
  • Use positional tokens in localized templates: Never hardcode sequential tokens like %@ %d in translation catalogs. Use %1$@ %2$d to allow translators to rearrange sentences safely.
  • Prefer .formatted() for user-facing UI: Reserve String(format:) for non-localized formatting tasks such as hex codes, network headers, or debugging logs. Use FormatStyle for currency, percentages, and user-facing floating-point values.

FAQ

What is the difference between %d and %ld in Swift?

In 64-bit iOS and macOS environments, Swift’s Int is a 64-bit integer, which technically corresponds to %ld. While String(format:) bridges Swift Int cleanly with both %d and %ld, using %ld or %lld avoids truncation risks when formatting explicit Int64 values.

How do you format a floating-point number without rounding up?

String(format: "%.2f", value) automatically rounds to the nearest specified decimal place. If you need truncation without rounding, use Decimal with a NSDecimalNumberHandler configured with .down, or multiply, truncate with floor(), and divide before formatting.

Can you format Swift Strings with String(format:) without Foundation?

No. The String(format:_:) initializer is an extension added to Swift’s standard library String by Apple’s Foundation framework. You must write import Foundation at the top of your Swift source file to use it.

Sources & credits

Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.