Quick tip before I finish moving boxes: stop creating new DateFormatters.
While unpacking boxes this week after a cross-country move, I ran into something that keeps showing up — not in my garage, but in Swift code.
This post will be a brief one, but worth your read.
DateFormatter() … everywhere.
It’s one of those quiet performance hits (the kind that make QA say “why’s this screen lagging?”) that sneak into reducers and SwiftUI views like a stowaway in a carry-on.
I’ve seen this happen in flight-status updates, QA builds, even interviews. Someone says, “the timeline feels laggy,” and sure enough — new formatters are being born every render.
🧩 The Problem
Here’s a familiar SwiftUI pattern:
struct FlightView: View {
let date: Date
var body: some View {
Text(DateFormatter().string(from: date)) // ❌
}
}
At first glance, nothing looks wrong.
But each body re-evaluation spawns a new DateFormatter, and those things aren’t light. They load locale info, calendars, and pattern metadata every single time.
Multiply that by a scrolling list of flights or timestamps, and you’ve got a jittery app.
⚙️ The Fix (The Five-Line Upgrade)
extension DateFormatter {
static let flightTime: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "MMM d, h:mm a"
return formatter
}()
}```
Use it anywhere:
```swift
Text(DateFormatter.flightTime.string(from: date))Created once, reused forever, and thread-safe thanks to Swift’s static initialization model.
🧬 In the Wild (TCA Example)
Here’s a before-and-after straight from a reducer I’ve seen more than once:
// ❌ Slow version
case .updateLastRefreshed(let date):
let formatter = DateFormatter()
formatter.dateFormat = "MMM d, h:mm a"
state.lastRefreshed = formatter.string(from: date)
return .nonevs.
// ✅ Clean version
case .updateLastRefreshed(let date):
state.lastRefreshed = DateFormatter.flightTime.string(from: date)
return .noneThat’s a small change that saves hundreds of unnecessary allocations in an app that updates timestamps often.
💸 Bonus: Beyond Dates
This trick also works with NumberFormatter, MeasurementFormatter, and ByteCountFormatter.
They all benefit from static caching, especially when you’re formatting data repeatedly (like prices, weights, or storage sizes).
🦦 Why It Matters
Performance is rarely about big refactors. It’s about small, thoughtful habits that compound. Little stuff like this adds up — it’s how you make an app feel like it belongs on iOS. Static formatters are one of those habits — invisible to users, but felt everywhere.
🎯 Bonus: More Real-World iOS Survival Stories If you’re hungry for more tips, tricks, and battle-tested stories from the trenches of native mobile development, swing by: https://medium.com/@wesleymatlock.
Let’s keep building apps that rock — and if you’ve ever been burned by a rogue formatter, share your story. 🤘