How to Build an Expense Tracker App in SwiftUI
An Expense Tracker lets users log daily spending by title, amount, and category — then visualise trends with Charts. It's perfect for indie developers who want a focused, data-driven first App Store release.
Prerequisites
- Xcode 16 or later installed on macOS Sequoia or higher
- Basic familiarity with Swift syntax and the SwiftUI view lifecycle
- An Apple Developer account (free tier is fine for Simulator; paid required for device & App Store)
Step-by-Step Guide
1. Define the SwiftData model
Mark your Transaction class with @Model so SwiftData automatically persists every expense to disk.
import SwiftData
import Foundation
@Model
final class Transaction {
var title: String
var amount: Double
var category: String
var date: Date
init(title: String, amount: Double,
category: String, date: Date = .now) {
self.title = title
self.amount = amount
self.category = category
self.date = date
}
}
2. Build the transaction list UI
Use @Query to fetch transactions in reverse-chronological order and render them in a List inside a NavigationStack.
import SwiftUI
import SwiftData
struct TransactionListView: View {
@Query(sort: \Transaction.date, order: .reverse)
var transactions: [Transaction]
@State private var showAdd = false
var body: some View {
NavigationStack {
List(transactions) { item in
HStack {
VStack(alignment: .leading) {
Text(item.title).font(.headline)
Text(item.category)
.font(.caption).foregroundStyle(.secondary)
}
Spacer()
Text(item.amount,
format: .currency(code: "USD"))
}
}
.navigationTitle("Expenses")
.toolbar {
Button("Add", systemImage: "plus") { showAdd = true }
}
.sheet(isPresented: $showAdd) { AddTransactionView() }
}
}
}
3. Implement transaction logging with categories
Present a Form sheet where the user enters a title, amount, and selects a category before saving to the model context.
import SwiftUI
import SwiftData
struct AddTransactionView: View {
@Environment(\.modelContext) private var context
@Environment(\.dismiss) private var dismiss
@State private var title = ""
@State private var amount = ""
@State private var category = "Food"
let categories = ["Food","Transport","Shopping","Health","Other"]
var body: some View {
NavigationStack {
Form {
TextField("Title", text: $title)
TextField("Amount", text: $amount)
.keyboardType(.decimalPad)
Picker("Category", selection: $category) {
ForEach(categories, id: \.self) { Text($0) }
}
}
.navigationTitle("Add Expense")
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("Save") {
let tx = Transaction(
title: title,
amount: Double(amount) ?? 0,
category: category)
context.insert(tx)
dismiss()
}.disabled(title.isEmpty || amount.isEmpty)
}
}
}
}
}
Common Pitfalls
- Forgetting
.modelContainerinApp— SwiftData won't initialise without attaching.modelContainer(for: Transaction.self)on yourWindowGroup. - Storing currency as
Doublein production — for a beginner projectDoubleis fine, but ship-ready apps should useDecimalor store amounts in integer cents to avoid floating-point drift. - Skipping iCloud sync — enable CloudKit in your entitlements early; retrofitting it after launch forces a migration and risks data loss for existing users.
Monetization: One-Time Purchase
The cleanest monetization for an Expense Tracker is a single one-time unlock — users pay once and own the full app forever, which aligns perfectly with a local-first, privacy-focused product. Implement it with StoreKit 2: declare a Non-Consumable IAP in App Store Connect, call Product.products(for:) at launch to fetch the SKU, gate premium views (e.g., the Charts spending breakdown) behind a isPurchased flag you persist in @AppStorage, and restore purchases automatically via Transaction.currentEntitlements on subsequent launches — no server required.
Ship Faster with Soarias
Soarias is a $79 one-time Mac app built for Claude Code users that scaffolds your entire SwiftUI + SwiftData project — models, views, StoreKit paywall, fastlane lanes, and App Store metadata — in minutes instead of days. For a beginner project like this Expense Tracker, Soarias can cut your setup time from several hours down to under 20 minutes, letting you focus on refining your category UI and Charts visualisations rather than wrestling with provisioning profiles and Info.plist entries.
Related Tutorials
FAQ
Do I need an Apple Developer account to follow this tutorial?
You can build and run the app on the iOS Simulator for free without any account. A paid Apple Developer account ($99/year) is only required when you want to install the app on a physical device via TestFlight or submit it to the App Store.
How do I submit my Expense Tracker to the App Store?
Archive your app in Xcode (Product → Archive), then use the Organizer to upload to App Store Connect. Fill in your app's metadata, screenshots, and privacy nutrition labels, attach your one-time purchase IAP, and submit for review. Apple typically reviews beginner apps within 24–48 hours. Soarias automates the fastlane setup and screenshot generation to make this process significantly faster.
Last reviewed: 2026-05-12 by the Soarias team.