Creating an Expense Tracker with Charts and Categories
Course: iOS App Development
Topic: Creating an Expense Tracker with Charts and Categories
Course Link: iOS App Development Course
Demo Link: Register for a Course Demo
Website: JustAcademy
1. Introduction to an Expense Tracker Application
An Expense Tracker is an iOS application that helps users record, organize, monitor, and analyze their daily spending. It allows users to enter expenses, assign categories, view transaction histories, and understand their financial habits through charts and summaries.
Building an Expense Tracker is a practical iOS development project because it combines several important technologies, including Swift, SwiftUI, data modeling, local persistence, forms, navigation, filtering, and data visualization.
Main Features of an Expense Tracker
- Add, edit, and delete expense records.
- Organize expenses into categories such as Food, Travel, Shopping, and Bills.
- Display total spending for a selected period.
- Show category-wise spending through charts.
- Display daily, weekly, and monthly expense trends.
- Filter transactions by category and date.
- Search expenses using descriptions or notes.
- Store records locally so they remain available after restarting the app.
- Display spending summaries and recent transactions.
2. Technologies Used to Build the Application
A modern Expense Tracker can be developed using Apple's native frameworks. SwiftUI provides the user interface, SwiftData or Core Data can manage local persistence, and Swift Charts can visualize financial information.
| Technology |
Purpose |
| Swift |
Implements application logic, calculations, models, and business rules. |
| SwiftUI |
Builds screens, forms, lists, buttons, navigation, and dashboard layouts. |
| SwiftData |
Provides model-based local data persistence for supported deployment targets. |
| Core Data |
Alternative framework for managing persistent structured application data. |
| Swift Charts |
Creates bar charts, line charts, and category distribution charts. |
| Foundation |
Provides dates, formatting, collections, and other standard utilities. |
| Xcode |
Provides the development, testing, debugging, and build environment. |
Recommended Architecture
A simple project can use a model-driven SwiftUI structure. Separate models, reusable views, and calculation logic so that the application remains easy to maintain as features grow.
ExpenseTracker/
|
|-- Models/
| |-- Expense.swift
| |-- ExpenseCategory.swift
|
|-- Views/
| |-- DashboardView.swift
| |-- AddExpenseView.swift
| |-- ExpenseListView.swift
| |-- CategoryView.swift
| |-- ChartsView.swift
|
|-- Services/
| |-- ExpenseCalculator.swift
| |-- CurrencyFormatter.swift
|
|-- Utilities/
| |-- DateFilter.swift
|
|-- ExpenseTrackerApp.swift
3. Planning the Expense Tracker User Interface
Before writing code, identify the screens and the information each screen should display. A clear screen structure makes the application easier to use and simplifies implementation.
Important Application Screens
| Screen |
Responsibilities |
| Dashboard |
Displays total spending, summaries, category charts, and recent transactions. |
| Add Expense |
Allows users to enter an amount, description, date, and category. |
| Expense History |
Displays saved expense records in a list. |
| Categories |
Displays available expense categories and their totals. |
| Analytics |
Shows spending patterns through charts and date filters. |
| Settings |
Provides preferences such as currency and data management options. |
Example Navigation Structure
Expense Tracker
|
|-- Dashboard
| |-- Total Expenses
| |-- Category Chart
| |-- Monthly Trend
|
|-- Transactions
| |-- Expense Details
| |-- Edit Expense
|
|-- Add Expense
|
|-- Categories
|
|-- Settings
4. Designing the Expense Data Model
The expense model represents one financial transaction. Each record should contain enough information to calculate totals, display transaction details, and group expenses into categories.
Important Expense Properties
| Property |
Data Type |
Purpose |
| id |
UUID |
Uniquely identifies an expense. |
| title |
String |
Describes the transaction. |
| amount |
Decimal |
Stores the expense amount accurately. |
| date |
Date |
Records when the expense occurred. |
| category |
String or relationship |
Identifies the expense category. |
| notes |
String |
Stores additional transaction details. |
| paymentMethod |
String or enum |
Records cash, card, UPI, or another payment method. |
Example Expense Model Using SwiftData
import Foundation
import SwiftData
@Model
final class Expense {
var id: UUID
var title: String
var amount: Decimal
var date: Date
var category: String
var notes: String
var paymentMethod: String
init(
title: String,
amount: Decimal,
date: Date = Date(),
category: String,
notes: String = "",
paymentMethod: String = "Cash"
) {
self.id = UUID()
self.title = title
self.amount = amount
self.date = date
self.category = category
self.notes = notes
self.paymentMethod = paymentMethod
}
}
Why Use Decimal for Money?
Financial calculations should avoid unnecessary binary floating-point rounding errors. Swift's Decimal type is useful for representing monetary amounts and performing financial calculations.
For applications requiring strict financial accuracy, also define a consistent currency policy, rounding rules, and storage strategy. Do not assume every currency uses two decimal places.
5. Creating Expense Categories
Categories organize transactions into meaningful groups. They help users understand where their money is going and make category-wise analysis possible.
Common Expense Categories
| Category |
Example Expenses |
Suggested SF Symbol |
| Food |
Restaurants, groceries, snacks. |
fork.knife |
| Travel |
Taxi, bus, train, fuel. |
car |
| Shopping |
Clothing, electronics, accessories. |
bag |
| Bills |
Electricity, internet, mobile bills. |
doc.text |
| Entertainment |
Movies, games, subscriptions. |
film |
| Health |
Medicines, consultations, fitness. |
heart |
| Education |
Books, courses, learning materials. |
book |
| Other |
Miscellaneous transactions. |
ellipsis.circle |
Creating a Category Enum
For a simple application with fixed categories, an enum provides a convenient way to define category values.
import Foundation
enum ExpenseCategory: String, CaseIterable, Identifiable {
case food = "Food"
case travel = "Travel"
case shopping = "Shopping"
case bills = "Bills"
case entertainment = "Entertainment"
case health = "Health"
case education = "Education"
case other = "Other"
var id: String { rawValue }
}
Benefits of Using an Enum
- Prevents inconsistent category spelling.
- Provides a fixed set of category choices.
- Makes Picker controls easier to implement.
- Supports predictable grouping and filtering.
- Reduces errors caused by arbitrary category text.
For applications that allow users to create, rename, recolor, or delete categories, use a persistent category model instead of relying only on a fixed enum.
6. Building the Add Expense Form
The Add Expense screen allows users to record a transaction. A well-designed form should validate the amount, provide category selection, and make date entry straightforward.
Form Fields
- Expense title or description.
- Amount.
- Category selection.
- Transaction date.
- Payment method.
- Optional notes.
Example Add Expense View
import SwiftUI
struct AddExpenseView: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.modelContext) private var context
@State private var title = ""
@State private var amountText = ""
@State private var category = ExpenseCategory.food.rawValue
@State private var date = Date()
@State private var paymentMethod = "Cash"
@State private var notes = ""
var body: some View {
NavigationStack {
Form {
Section("Expense Details") {
TextField("Expense title", text: $title)
TextField("Amount", text: $amountText)
.keyboardType(.decimalPad)
Picker("Category", selection: $category) {
ForEach(ExpenseCategory.allCases) { item in
Text(item.rawValue)
.tag(item.rawValue)
}
}
DatePicker(
"Date",
selection: $date,
displayedComponents: .date
)
}
Section("Payment") {
Picker("Payment Method", selection: $paymentMethod) {
Text("Cash").tag("Cash")
Text("Card").tag("Card")
Text("UPI").tag("UPI")
Text("Other").tag("Other")
}
TextField("Notes (Optional)", text: $notes)
}
Button("Save Expense") {
saveExpense()
}
.disabled(!isValid)
}
.navigationTitle("Add Expense")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Cancel") {
dismiss()
}
}
}
}
}
private var isValid: Bool {
guard let amount = Decimal(string: amountText) else {
return false
}
return !title.trimmingCharacters(
in: .whitespacesAndNewlines
).isEmpty && amount > 0
}
private func saveExpense() {
guard let amount = Decimal(string: amountText),
isValid else {
return
}
let expense = Expense(
title: title,
amount: amount,
date: date,
category: category,
notes: notes,
paymentMethod: paymentMethod
)
context.insert(expense)
dismiss()
}
}
Form Validation Rules
- Expense title should not be empty.
- Amount must be a valid positive number.
- Category must be selected.
- Date should be valid and appropriate for the application's rules.
- Optional notes should not prevent saving.
- Invalid input should produce clear feedback.
7. Saving Expenses with SwiftData
SwiftData provides a model-based approach to storing application data. It integrates with SwiftUI and supports querying model objects, inserting records, editing records, and deleting records.
Configure the Model Container
import SwiftUI
import SwiftData
@main
struct ExpenseTrackerApp: App {
var body: some Scene {
WindowGroup {
DashboardView()
}
.modelContainer(for: Expense.self)
}
}
Important SwiftData Components
| Component |
Purpose |
| @Model |
Marks a class as a persistent model. |
| modelContainer |
Configures the persistent storage container for the application. |
| @Query |
Fetches model records and updates SwiftUI views when relevant data changes. |
| modelContext |
Provides access to insert, delete, and manage model objects. |
| context.insert() |
Adds a new model object to the persistence context. |
| context.delete() |
Marks a model object for deletion. |
Fetching Expenses
import SwiftUI
import SwiftData
struct ExpenseListView: View {
@Query(sort: \Expense.date, order: .reverse)
private var expenses: [Expense]
var body: some View {
List(expenses) { expense in
VStack(alignment: .leading) {
Text(expense.title)
.font(.headline)
Text(expense.category)
.font(.subheadline)
Text(expense.amount.description)
}
}
.navigationTitle("Expenses")
}
}
SwiftData manages persistence through its model context. For production applications, define appropriate model relationships, migration strategies, validation rules, and error-handling behavior.
8. Displaying Expenses in a List
The transaction list provides users with a chronological view of their spending. Each row should show the transaction title, category, date, and formatted amount.
Example Expense Row
struct ExpenseRowView: View {
let expense: Expense
var body: some View {
HStack(spacing: 12) {
Image(systemName: "creditcard")
.font(.title2)
.frame(width: 42, height: 42)
.background(
Color.blue.opacity(0.12)
)
.clipShape(RoundedRectangle(cornerRadius: 10))
VStack(alignment: .leading, spacing: 4) {
Text(expense.title)
.font(.headline)
Text(expense.category)
.font(.caption)
.foregroundStyle(.secondary)
Text(expense.date, style: .date)
.font(.caption2)
.foregroundStyle(.secondary)
}
Spacer()
Text(expense.amount.formatted())
.font(.headline)
}
.padding(.vertical, 5)
}
}
List Features to Implement
- Sort records by newest or oldest date.
- Search using expense titles and notes.
- Filter by category.
- Display formatted currency values.
- Allow users to open transaction details.
- Provide edit and delete actions.
- Show a helpful empty state when there are no expenses.
9. Editing and Deleting Expenses
Users should be able to correct transaction details and remove records they no longer need. Editing should update the existing model rather than creating an unnecessary duplicate.
Editing an Expense
- Allow the user to select an expense from the list.
- Open an edit form populated with the existing values.
- Allow the user to modify the title, amount, date, category, and notes.
- Validate the updated information.
- Save changes to the existing model.
- Refresh the dashboard and charts using the updated data.
Deleting an Expense
import SwiftUI
import SwiftData
struct ExpenseManagementView: View {
@Environment(\.modelContext) private var context
@Query private var expenses: [Expense]
var body: some View {
List {
ForEach(expenses) { expense in
ExpenseRowView(expense: expense)
}
.onDelete(perform: deleteExpenses)
}
}
private func deleteExpenses(at offsets: IndexSet) {
for index in offsets {
context.delete(expenses[index])
}
}
}
In a production app, consider confirming destructive actions and ensuring that deleting an expense updates all summaries and charts immediately.
10. Calculating Total Expenses
The dashboard should calculate total spending for the selected date range. Calculations should use the same filtering rules as the transaction list and charts so that all screens display consistent information.
Basic Total Calculation
func totalExpenses(_ expenses: [Expense]) -> Decimal {
expenses.reduce(Decimal.zero) {
$0 + $1.amount
}
}
Calculating Monthly Expenses
func monthlyExpenses(
from expenses: [Expense],
month: Date,
calendar: Calendar = .current
) -> Decimal {
guard let interval = calendar.dateInterval(
of: .month,
for: month
) else {
return .zero
}
return expenses
.filter {
$0.date >= interval.start &&
$0.date < interval.end
}
.reduce(Decimal.zero) {
$0 + $1.amount
}
}
Useful Dashboard Metrics
- Total expenses for the current month.
- Total expenses for the previous month.
- Average daily spending.
- Highest spending category.
- Number of recorded transactions.
- Recent expense activity.
11. Grouping Expenses by Category
Category grouping combines individual transactions into meaningful totals. For example, if a user records three food expenses of ₹200, ₹350, and ₹150, the total Food category spending is ₹700.
Category Grouping Logic
func categoryTotals(
from expenses: [Expense]
) -> [String: Decimal] {
Dictionary(grouping: expenses, by: \.category)
.mapValues { items in
items.reduce(Decimal.zero) {
$0 + $1.amount
}
}
}
Example Category Summary
| Category |
Expenses |
Total |
| Food |
3 |
₹700 |
| Travel |
2 |
₹500 |
| Shopping |
1 |
₹1,200 |
| Bills |
2 |
₹1,000 |
| Total |
8 |
₹3,400 |
12. Introduction to Swift Charts
Swift Charts is Apple's framework for building data visualizations in SwiftUI. It provides chart components such as BarMark, LineMark, PointMark, and SectorMark. Developers can combine these components to display trends, comparisons, and category distributions.
For an Expense Tracker, Swift Charts can visualize monthly spending, daily expenses, and the proportion of total expenses assigned to each category.
Common Chart Types
| Chart Type |
Purpose |
Expense Tracker Example |
| Bar Chart |
Compares values across groups. |
Compare total expenses by category. |
| Line Chart |
Displays changes over time. |
Show daily or monthly spending trends. |
| Donut Chart |
Displays parts of a whole. |
Show category contribution to total spending. |
| Area Chart |
Highlights changes and overall magnitude over time. |
Show cumulative spending trends. |
Importing Swift Charts
import SwiftUI
import Charts
13. Creating a Category Spending Bar Chart
A bar chart makes it easy to compare how much money users spend in different categories. Each bar represents a category, and its height represents the total expense amount.
Define Chart Data
struct CategoryExpense: Identifiable {
let id = UUID()
let category: String
let total: Double
}
Example Bar Chart
import SwiftUI
import Charts
struct CategoryBarChartView: View {
let data: [CategoryExpense]
var body: some View {
Chart(data) { item in
BarMark(
x: .value("Category", item.category),
y: .value("Amount", item.total)
)
.foregroundStyle(by: .value(
"Category",
item.category
))
.cornerRadius(6)
}
.frame(height: 250)
.chartYAxis {
AxisMarks(position: .leading)
}
.chartLegend(position: .bottom)
}
}
Advantages of Bar Charts
- Compare category spending quickly.
- Identify the highest spending category.
- Highlight differences between expense groups.
- Support category-level financial decisions.
14. Creating a Donut Chart for Category Distribution
A donut chart shows how the total expense amount is divided among categories. It is useful when users want to understand the relative contribution of Food, Travel, Shopping, Bills, and other categories.
Donut Chart Implementation
import SwiftUI
import Charts
struct CategoryDonutChartView: View {
let data: [CategoryExpense]
var body: some View {
Chart(data) { item in
SectorMark(
angle: .value("Amount", item.total),
innerRadius: .ratio(0.58),
angularInset: 2
)
.foregroundStyle(by: .value(
"Category",
item.category
))
}
.frame(height: 280)
.chartLegend(position: .bottom)
}
}
Understanding SectorMark
| Property |
Purpose |
| angle |
Controls the size of a sector based on its value. |
| innerRadius |
Creates the central hole in a donut chart. |
| angularInset |
Controls spacing between sectors. |
| foregroundStyle |
Provides visual category differentiation. |
15. Creating a Monthly Expense Trend Line Chart
A line chart shows how spending changes over time. It can help users identify days or months when expenses increase or decrease.
Define Monthly Data
struct MonthlyExpense: Identifiable {
let id = UUID()
let month: Date
let total: Double
}
Line Chart Implementation
import SwiftUI
import Charts
struct MonthlyTrendChartView: View {
let data: [MonthlyExpense]
var body: some View {
Chart(data) { item in
LineMark(
x: .value("Month", item.month),
y: .value("Expense", item.total)
)
.interpolationMethod(.catmullRom)
PointMark(
x: .value("Month", item.month),
y: .value("Expense", item.total)
)
}
.frame(height: 260)
.chartXAxis {
AxisMarks(values: .automatic(desiredCount: 6)) {
AxisValueLabel(format: .dateTime.month(.abbreviated))
}
}
}
}
Where Line Charts Are Useful
- Comparing monthly spending.
- Monitoring daily expenses.
- Identifying spending spikes.
- Reviewing long-term financial habits.
- Comparing current and previous periods.
16. Preparing Data for Charts
Charts should use aggregated data rather than displaying every raw transaction as a separate category total. Before creating a chart, filter the expense records and group them according to the required reporting period or category.
Convert Decimal Totals for Chart Display
func chartData(
from expenses: [Expense]
) -> [CategoryExpense] {
let totals = categoryTotals(from: expenses)
return totals.map { category, amount in
CategoryExpense(
category: category,
total: NSDecimalNumber(
decimal: amount
).doubleValue
)
}
.sorted {
$0.total > $1.total
}
}
Use Decimal for financial calculations and convert to Double only at the chart presentation boundary when needed by the chart's numeric values. Avoid using chart display values as the source of financial calculations.
Chart Data Preparation Steps
- Fetch expense records from persistent storage.
- Apply the selected date range.
- Apply any selected category filters.
- Group the filtered records.
- Calculate totals using Decimal.
- Convert values for visualization when required.
- Sort the results for meaningful presentation.
- Display the resulting chart.
17. Building a Dashboard Screen
The dashboard is the main screen of the Expense Tracker. It combines financial summaries, category charts, monthly trends, and recent transaction records.
Recommended Dashboard Components
- Total expense summary card.
- Selected reporting period.
- Category spending chart.
- Monthly trend chart.
- Top spending category.
- Recent transaction list.
- Add Expense button.
Example Dashboard Structure
import SwiftUI
import SwiftData
struct DashboardView: View {
@Query(sort: \Expense.date, order: .reverse)
private var expenses: [Expense]
var body: some View {
NavigationStack {
ScrollView {
VStack(spacing: 20) {
Text("Expense Dashboard")
.font(.largeTitle.bold())
Text("Total Spending")
.font(.headline)
Text(totalExpenses(expenses).formatted())
.font(.title.bold())
Text("Category Breakdown")
.font(.headline)
CategoryDonutChartView(
data: chartData(from: expenses)
)
Text("Recent Transactions")
.font(.headline)
ForEach(expenses.prefix(5)) { expense in
ExpenseRowView(expense: expense)
}
}
.padding()
}
.navigationTitle("Overview")
}
}
}
18. Filtering Expenses by Date
Date filtering allows users to review expenses over specific periods. Common options include Today, This Week, This Month, Previous Month, and Custom Range.
Common Date Filters
| Filter |
Purpose |
| Today |
Displays expenses recorded on the current calendar day. |
| This Week |
Displays expenses within the current calendar week. |
| This Month |
Displays expenses within the current calendar month. |
| Last Month |
Displays expenses from the previous calendar month. |
| Last 30 Days |
Displays expenses within a rolling 30-day period. |
| Custom Range |
Displays expenses between user-selected dates. |
Filtering by Month
func expensesForMonth(
_ expenses: [Expense],
date: Date,
calendar: Calendar = .current
) -> [Expense] {
guard let interval = calendar.dateInterval(
of: .month,
for: date
) else {
return []
}
return expenses.filter {
$0.date >= interval.start &&
$0.date < interval.end
}
}
Why Use Calendar APIs?
- They account for calendar boundaries.
- They support different locales and calendar settings.
- They avoid fragile manual date comparisons.
- They make week and month filtering easier to maintain.
19. Filtering Expenses by Category
Category filtering helps users focus on one type of spending. For example, a user may want to see only Travel expenses to understand transportation costs.
Category Filter Example
func expenses(
_ expenses: [Expense],
in category: String
) -> [Expense] {
expenses.filter {
$0.category == category
}
}
Combining Date and Category Filters
func filteredExpenses(
_ expenses: [Expense],
category: String?,
startDate: Date,
endDate: Date
) -> [Expense] {
expenses.filter { expense in
let matchesCategory =
category == nil ||
expense.category == category
let matchesDate =
expense.date >= startDate &&
expense.date < endDate
return matchesCategory && matchesDate
}
}
When filtering by date, define whether the end date is inclusive or exclusive. Using an exclusive end boundary often makes date-range calculations more predictable.
20. Searching Expense Records
Search allows users to locate transactions quickly. The application can search titles, notes, category names, or other suitable text fields.
Example Search Logic
func searchExpenses(
_ expenses: [Expense],
query: String
) -> [Expense] {
let searchText = query
.trimmingCharacters(in: .whitespacesAndNewlines)
guard !searchText.isEmpty else {
return expenses
}
return expenses.filter {
$0.title.localizedCaseInsensitiveContains(searchText) ||
$0.category.localizedCaseInsensitiveContains(searchText) ||
$0.notes.localizedCaseInsensitiveContains(searchText)
}
}
21. Displaying Currency and Financial Values
Currency formatting ensures that amounts are displayed in a familiar format. A financial application should consider the user's currency, locale, grouping separators, decimal precision, and currency symbol.
Currency Formatting Example
import Foundation
func currencyText(
amount: Decimal,
locale: Locale = .current
) -> String {
amount.formatted(
.currency(code: "INR")
.locale(locale)
)
}
let amount: Decimal = 1250.50
print(currencyText(amount: amount))
Currency Formatting Best Practices
- Use an explicit currency code when the application supports a defined currency.
- Use locale-aware formatting for grouping and decimal separators.
- Do not manually concatenate currency symbols with numbers.
- Do not assume all currencies use two decimal places.
- Keep stored monetary values separate from their display formatting.
- Define how the application handles currency conversion if multiple currencies are supported.
22. Adding Category Colors and Icons
Colors and icons make categories easier to recognize. Each category can have a consistent visual identity that appears in transaction rows, category selectors, and charts.
Example Category Icon Mapping
func categoryIcon(_ category: String) -> String {
switch category {
case "Food":
return "fork.knife"
case "Travel":
return "car"
case "Shopping":
return "bag"
case "Bills":
return "doc.text"
case "Entertainment":
return "film"
case "Health":
return "heart"
case "Education":
return "book"
default:
return "ellipsis.circle"
}
}
Visual Design Guidelines
- Use the same icon for a category throughout the application.
- Choose colors with sufficient contrast.
- Do not rely on color alone to communicate category information.
- Include readable category labels in chart legends.
- Use SF Symbols that clearly represent the category.
23. Creating Reusable Summary Cards
Summary cards display important financial metrics in a compact format. Reusable components make it easier to maintain a consistent dashboard design.
Example Summary Card
struct SummaryCardView: View {
let title: String
let amount: Decimal
let symbol: String
var body: some View {
VStack(alignment: .leading, spacing: 10) {
Label(title, systemImage: symbol)
.font(.subheadline)
.foregroundStyle(.secondary)
Text(amount.formatted(.currency(code: "INR")))
.font(.title2.bold())
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(.thinMaterial)
.clipShape(RoundedRectangle(cornerRadius: 16))
}
}
24. Handling Empty States and No Chart Data
When an application has no expense records, charts may have no values to display. The dashboard should explain the situation and guide the user toward adding the first transaction.
Empty State Examples
- No expenses recorded: "Add your first expense to start tracking spending."
- No category results: "There are no expenses in this category."
- No monthly data: "No transactions found for this month."
- No search results: "Try another search term."
Example Empty State View
ContentUnavailableView(
"No Expenses Yet",
systemImage: "chart.bar",
description: Text(
"Add your first expense to see your spending summary."
)
)
25. Improving Chart Accessibility
Charts should be understandable to users who rely on assistive technologies. Swift Charts supports accessibility features, and developers should provide meaningful descriptions and ensure that important financial information is also available in text form.
Accessibility Best Practices
- Provide descriptive chart titles.
- Use clear axis labels and category names.
- Provide VoiceOver-friendly descriptions.
- Do not communicate differences using color alone.
- Ensure chart values are available in accessible text summaries.
- Use readable text sizes and sufficient contrast.
- Test the application with VoiceOver.
26. Testing the Expense Tracker
Testing ensures that financial calculations, data persistence, filtering, and charts behave correctly. Since the application handles monetary values, calculation accuracy is particularly important.
Functional Testing
- Add an expense with a valid amount.
- Attempt to save an empty expense title.
- Enter an invalid or negative amount.
- Edit an existing expense.
- Delete an expense.
- Restart the application and verify saved records.
- Filter expenses by category.
- Filter expenses by date range.
- Verify category totals.
- Verify monthly totals.
- Check that charts update after records are edited or deleted.
Example Calculation Test Cases
| Test Case |
Input |
Expected Result |
| Single expense |
₹500 |
₹500 total |
| Multiple expenses |
₹200 + ₹300 |
₹500 total |
| Category total |
Food: ₹200 + ₹150 |
₹350 Food total |
| Empty records |
No expenses |
Zero total and empty state |
| Deleted transaction |
Remove ₹100 expense |
Total decreases by ₹100 |
27. Performance and Data Management Best Practices
- Use efficient queries when the transaction history becomes large.
- Aggregate chart data instead of repeatedly calculating totals in every view.
- Keep financial calculation logic separate from UI components.
- Use appropriate persistence models and relationships.
- Avoid unnecessary chart recomputation.
- Use stable identifiers for categories and expenses.
- Handle data migration when the model structure changes.
- Consider backup and export options for important user records.
- Protect sensitive financial information.
- Test performance with realistic transaction volumes.
28. Common Problems and Troubleshooting
| Problem |
Possible Cause |
Solution |
| Expense does not save |
Model context is unavailable or validation fails. |
Check model container setup, context access, and validation logic. |
| Expense disappears after restart |
Persistence is not configured correctly. |
Verify SwiftData model container and persistent storage configuration. |
| Category total is incorrect |
Incorrect grouping or filtering logic. |
Review category keys and calculation boundaries. |
| Chart is empty |
No chart data or incorrect aggregation. |
Inspect the filtered data before creating chart marks. |
| Chart does not update |
View is not observing the updated data or chart input is stale. |
Use reactive data fetching and derive chart data from current records. |
| Currency appears incorrectly |
Incorrect locale or currency code. |
Use explicit currency formatting and verify locale settings. |
| Date filter misses records |
Incorrect start or end date boundaries. |
Use Calendar date intervals and consistent inclusive/exclusive rules. |
| Duplicate expenses appear |
New records are inserted when existing records should be updated. |
Update the existing model object during editing. |
| Chart labels overlap |
Too many categories or long labels. |
Use shorter labels, horizontal charts, or category filtering. |
29. Mini Project: Complete Expense Tracker Application
Project Objective: Build a functional iOS Expense Tracker that allows users to record transactions, organize spending by category, and understand their financial habits through charts.
Project Requirements
- Create a SwiftUI application with a dashboard.
- Design an Expense data model.
- Implement local persistence using SwiftData.
- Create default expense categories.
- Build an Add Expense form.
- Implement editing and deleting functionality.
- Display recent transactions.
- Calculate total spending.
- Group expenses by category.
- Create a category bar chart.
- Create a category donut chart.
- Create a monthly spending trend chart.
- Implement date and category filters.
- Add currency formatting.
- Provide empty states and input validation.
- Test the application on an iOS simulator and physical device.
Expected Application Output
At the end of the project, the application should provide a dashboard with total expenses, category-wise spending, monthly trends, and recent transactions. Users should be able to add, edit, delete, search, and filter expense records.
30. Interview Questions and Answers
Q1. What is an Expense Tracker application?
Answer: An Expense Tracker is an application that records financial transactions, organizes them into categories, calculates spending totals, and provides financial summaries.
Q2. Why is SwiftUI suitable for building an Expense Tracker?
Answer: SwiftUI provides declarative UI components such as forms, lists, navigation, and reusable views, making it suitable for building a modern financial dashboard.
Q3. What is SwiftData?
Answer: SwiftData is Apple's model-based persistence framework that allows developers to define models and manage persistent application data.
Q4. What is the purpose of the @Model macro?
Answer: The @Model macro makes a class a SwiftData persistent model so its instances can be stored and managed by a model container.
Q5. Why should categories be used in an Expense Tracker?
Answer: Categories group similar transactions and help users understand how much they spend on food, travel, shopping, bills, and other activities.
Q6. What is Swift Charts?
Answer: Swift Charts is Apple's framework for creating data visualizations such as bar charts, line charts, and sector charts in SwiftUI.
Q7. What is the purpose of BarMark?
Answer: BarMark represents values using rectangular bars and is useful for comparing expense totals across categories.
Q8. What is SectorMark?
Answer: SectorMark creates a chart sector and is useful for displaying category contributions in pie or donut charts.
Q9. Why is Decimal useful for monetary values?
Answer: Decimal is designed for decimal arithmetic and helps avoid common binary floating-point precision issues in financial calculations.
Q10. How can monthly expenses be calculated?
Answer: Filter expenses within the selected month's Calendar date interval and add their monetary amounts.
Q11. How can category-wise totals be calculated?
Answer: Group expense records by category and calculate the sum of each group's amounts.
Q12. Why should chart data be aggregated?
Answer: Aggregation converts individual transactions into meaningful summaries, making charts easier to read and more efficient to display.
Q13. How can an application support date filtering?
Answer: Use Calendar APIs to calculate the required date interval and filter records using consistent start and end boundaries.
Q14. How do you handle empty chart data?
Answer: Display an empty state with a clear message and guide the user to add transactions or change the selected filter.
Q15. How can an Expense Tracker maintain accurate totals?
Answer: Use Decimal for financial calculations, centralize calculation logic, validate input, and ensure that filters and charts use consistent data.
31. Quick Revision Checklist
- Understand the Expense Tracker application workflow.
- Design the Expense data model.
- Understand the role of expense categories.
- Create forms using SwiftUI.
- Validate expense input.
- Store records using SwiftData.
- Fetch and display transactions.
- Implement edit and delete functionality.
- Calculate total and monthly expenses.
- Group expenses by category.
- Create charts using Swift Charts.
- Use BarMark for category comparisons.
- Use SectorMark for category distribution.
- Use LineMark for spending trends.
- Implement date and category filters.
- Format monetary values correctly.
- Test calculations, persistence, and chart updates.
32. Conclusion
Creating an Expense Tracker with Charts and Categories is an excellent practical project for learning iOS application development. It combines SwiftUI interface design, Swift data modeling, local persistence, transaction management, category grouping, and financial calculations.
By integrating Swift Charts, developers can transform transaction records into meaningful visual summaries. Category charts help users understand where their money goes, while monthly trend charts reveal changes in spending habits over time.
A well-designed Expense Tracker should prioritize accurate financial calculations, reliable data storage, clear navigation, accessible visualizations, and a smooth user experience. Completing this project gives learners practical experience with several important technologies used in modern iOS applications.
33. Official Documentation and Learning Resources