RateChecker
A SwiftUI iOS app that benchmarks your savings against live Treasury rates, forecasts where rates are headed with on-device Core ML, and shows exactly how much your current bank is costing you.
Problem
Most people keep money in a savings account without knowing whether it's earning a competitive rate. The gap between a 0.5% APY account and a 5%+ HYSA on $10,000 is $450 per year — silently lost. RateChecker was built to make that cost visible, pull in live Treasury and Fed data, and give users a clear path to better options — all without leaving the app.
Challenge-Based Learning
Challenge: Integrate live financial data from two government APIs, train an on-device Core ML model for rate forecasting, and surface actionable insights without overwhelming users new to personal finance.
Approach: Built a two-tab architecture separating personal finance ("My Money") from market intelligence ("Rates"), used a reusable InfoButton component to teach financial terms in context instead of a separate glossary, and offloaded rate forecasting entirely to a Core ML model running on device.
Outcome: A full-featured iOS app that combines real-time data, machine learning, background monitoring, and financial education in a single coherent experience.
Project Snapshot
- Platform: iOS (SwiftUI)
- Stack: SwiftUI · SwiftData · Core ML · WidgetKit · FRED API · US Treasury API
- Focus: Live financial data, on-device ML, background monitoring, financial education UX
- Team: Solo
- Role: iOS developer, ML integration, product design
- Timeline: April 2026
Key Features
- My Money Dashboard — add real savings accounts, see your blended APY, projected annual earnings, and the gap vs the T-Bill benchmark
- Savings Calculator — compare HYSA, CD, Money Market, and Bond Fund returns side-by-side with user-editable APY rates
- Better Options — curated top HYSAs and CDs from Wealthfront, Marcus, Ally, Discover, Fidelity, and Bread Financial
- Live Treasury Rates — T-Bills, T-Notes, T-Bonds, and TIPS from the US Treasury Fiscal Data API (no API key required)
- Economic Context — current inflation rate and Fed Funds rate from the FRED API
- Core ML Forecast — on-device model predicts T-Bill rates using 14 economic indicators
- Calculation History — all calculations saved with SwiftData for later review
- Home Screen Widget — live T-Bill rate always visible via WidgetKit
- Background Rate Monitoring — checks rates every 4 hours, sends local notifications when rates shift significantly
- Contextual InfoButton Glossary — every financial term has an inline info button opening a plain-English definition sheet, no separate tab needed
Tech Stack
| Layer | Technology |
|---|---|
| UI | SwiftUI — @State, @Query, @Environment, @AppStorage |
| Persistence | SwiftData — SavedCalculation, DepositSession, UserAccount models |
| Machine Learning | Core ML — on-device T-Bill rate forecasting (14 economic indicators) |
| Widget | WidgetKit — home screen T-Bill rate widget |
| Background | BGTaskScheduler, UNUserNotificationCenter |
| Networking | Async/await — no Combine |
| APIs | US Treasury Fiscal Data API, FRED API |
| Pattern | MVVM — SavingsCalculatorViewModel, HistoryViewModel |
Architecture
The app is split into two tabs to separate personal finance from market intelligence:
- My Money tab — dashboard (blended rate, gap vs benchmark, annual earnings), savings calculator, and user account management
- Rates tab — live Treasury rates, economic context (inflation + Fed Funds), Core ML forecast, and rate comparison chart
Supporting services:
- RateService + RateCache — handles all network requests with offline fallback
- NotificationService — manages rate change and inflation alerts
- BackgroundRateChecker — enum handling the full
BGAppRefreshTasklifecycle - InfoButton — reusable component used across all views to surface plain-English financial term definitions
On-Device Rate Forecasting with Core ML
Instead of hitting a server for predictions, RateChecker runs a trained Core ML model directly on device. The model takes 14 economic indicators as input — including current T-Bill rate, inflation, Fed Funds rate, and yield curve data — and outputs a forecasted T-Bill rate. This means predictions work offline and add zero server cost.
func forecastTBillRate(indicators: EconomicIndicators) -> Double? {
guard let model = try? TBillForecastModel(configuration: MLModelConfiguration()) else {
return nil
}
let input = TBillForecastModelInput(
currentRate: indicators.tBillRate,
inflationRate: indicators.cpi,
fedFundsRate: indicators.fedFunds,
yieldSpread: indicators.yieldSpread
// ... 10 additional indicators
)
let output = try? model.prediction(input: input)
return output?.forecastedRate
}
Contextual InfoButton — Financial Education Without a Glossary Tab
A key UX decision: instead of sending users to a separate glossary, every financial term (APY, T-Bill, Principal, Fed Funds Rate, etc.) has a small inline InfoButton. Tapping it opens a sheet with a plain-English definition. This keeps education in context — users learn the term at the exact moment they encounter it.
struct InfoButton: View {
let term: String
let definition: String
@State private var showSheet = false
var body: some View {
Button {
showSheet = true
} label: {
Image(systemName: "info.circle")
.foregroundStyle(.secondary)
}
.sheet(isPresented: $showSheet) {
TermDefinitionSheet(term: term, definition: definition)
}
}
}
// Usage anywhere in the app:
HStack {
Text("APY")
InfoButton(term: "APY", definition: "Annual Percentage Yield — the real rate of return on your savings, including compound interest over one year.")
}
Background Rate Monitoring
RateChecker checks rates every 4 hours using BGTaskScheduler — even when the app is closed. If a rate changes significantly, the app fires a local notification via UNUserNotificationCenter. The BackgroundRateChecker enum manages the full task lifecycle: registering, scheduling, executing, and re-scheduling after each run.
enum BackgroundRateChecker {
static let taskIdentifier = "com.geoclink.ratechecker.refresh"
static func scheduleRefresh() {
let request = BGAppRefreshTaskRequest(identifier: taskIdentifier)
request.earliestBeginDate = Date(timeIntervalSinceNow: 4 * 3600)
try? BGTaskScheduler.shared.submit(request)
}
static func handleRefresh(task: BGAppRefreshTask) {
scheduleRefresh() // re-schedule immediately
Task {
let newRate = await RateService.shared.fetchCurrentTBillRate()
if RateCache.shared.hasSignificantChange(newRate) {
NotificationService.shared.sendRateChangeAlert(rate: newRate)
}
task.setTaskCompleted(success: true)
}
}
}
Key Decisions
- Used the US Treasury Fiscal Data API (no key required) for live rates — lower friction, no credential management in the app
- Chose SwiftData over Core Data for persistence — cleaner Swift-native API that pairs naturally with
@Queryin SwiftUI - Ran ML forecasting on-device with Core ML rather than a hosted model — zero server cost, works offline, no latency
- Built the InfoButton as a reusable component rather than a glossary screen — keeps financial education in context where users actually need it
- Used async/await throughout with no Combine — simpler to read, easier to maintain, fits modern Swift concurrency
- Two-tab design cleanly separates personal finance ("My Money") from market data ("Rates") so neither area feels cluttered
Outcome
RateChecker demonstrates a full production-quality iOS app: live API integration, on-device machine learning, background task scheduling, local notifications, WidgetKit, SwiftData persistence, and a purposeful UX pattern (InfoButton) that teaches financial literacy without interrupting the flow. The app directly addresses a real financial problem most people don't know they have.
What I Learned
- Integrating Core ML into a SwiftUI app with a trained regression model
- Building and distributing a WidgetKit extension
- Managing background task lifecycle with
BGTaskScheduler - SwiftData modeling with relationships and
@Query-driven views - Consuming two government REST APIs (Treasury + FRED) with async/await
- Designing financial UX for users who aren't financial experts
Next Iteration
- App Store submission
- Retrain Core ML model with more historical data for improved forecast accuracy
- iPad and macOS (Catalyst) support
- Expand "Better Options" with live rate updates pulled directly from institution APIs