How to Build a Habit Tracker App with SwiftUI (2026)
Production-grade guide. SwiftData persistence, streak logic, calendar heatmap, push notification reminders, RevenueCat paywall.
To build a habit tracker iOS app with SwiftUI in 2026: (1) model habits + completions as SwiftData @Models, (2) compute streaks from completion dates rather than storing them, (3) schedule UNCalendarNotificationTrigger reminders per habit, (4) gate unlimited habits + themes via RevenueCat paywall, (5) ship in 4–7 days with The Swift Kit. The full architecture, schema, and Swift code is below.
Ship your SwiftUI app in 5 emails
A free 5-part course on the parts that actually stall launches: paywall, auth, onboarding, App Store review, and pricing. No fluff, unsubscribe anytime.
Ship your habit tracker this week.
The Swift Kit ships everything except the habit logic — design system, paywall, push notifications, all pre-wired.
Rather have it done for you? I set up and ship your app from $499. See how
The 7 Steps to Ship
Each step takes 4–8 hours with the Swift Kit. From scratch, double-to-triple that.
- 1
Step 1 — Project setup
Clone Swift Kit, run setup.sh, pick "Productivity" preset.
git clone <swift-kit-repo> MyHabitApp cd MyHabitApp ./setup.sh - 2
Step 2 — Model habits + completions in SwiftData
Habit owns a list of HabitCompletion records. Completion has a date (normalized to midnight). Use a @Model for native SwiftUI integration.
import SwiftData @Model final class Habit { var name: String var emoji: String var colorHex: String var reminderTime: Date? @Relationship(deleteRule: .cascade) var completions: [HabitCompletion] = [] var createdAt: Date init(name: String, emoji: String, colorHex: String) { self.name = name self.emoji = emoji self.colorHex = colorHex self.createdAt = .now } } @Model final class HabitCompletion { var date: Date // midnight in user timezone var habit: Habit? } - 3
Step 3 — Compute streaks (don't store them)
Walk backward from today, counting consecutive completed days. Computing on read is cheap and avoids stale-data bugs.
extension Habit { var currentStreak: Int { let dates = Set(completions.map { Calendar.current.startOfDay(for: $0.date) }) var streak = 0 var day = Calendar.current.startOfDay(for: .now) while dates.contains(day) { streak += 1 day = Calendar.current.date(byAdding: .day, value: -1, to: day)! } return streak } } - 4
Step 4 — Build the habit list + completion toggle
List of habits with a tap-to-complete circle. Toggling adds or removes today's HabitCompletion. Haptic + animation on complete.
List { ForEach(habits) { habit in HStack { Text(habit.emoji).font(.title) VStack(alignment: .leading) { Text(habit.name).font(.headline) Text("\(habit.currentStreak) day streak").font(.caption).foregroundStyle(.secondary) } Spacer() Button { toggle(habit) } label: { Image(systemName: isCompletedToday(habit) ? "checkmark.circle.fill" : "circle") .font(.title) } } } } - 5
Step 5 — Calendar heatmap view
GitHub-style calendar heat map showing last 90 days. Use LazyVGrid with 7 columns. Color intensity scales with completion.
LazyVGrid(columns: Array(repeating: .init(.fixed(20)), count: 7), spacing: 4) { ForEach(last90Days, id: \.self) { day in RoundedRectangle(cornerRadius: 4) .fill(color(for: day, habit: habit)) .frame(height: 20) } } - 6
Step 6 — Schedule push notification reminders
For each habit with a reminder time, schedule a daily UNCalendarNotificationTrigger. Cap at 64 (Apple's limit on pending notifications). Cancel today's when user marks complete.
func scheduleReminder(for habit: Habit) async throws { guard let time = habit.reminderTime else { return } let center = UNUserNotificationCenter.current() let content = UNMutableNotificationContent() content.title = habit.emoji + " " + habit.name content.body = "Time to log your habit" content.sound = .default let trigger = UNCalendarNotificationTrigger( dateMatching: Calendar.current.dateComponents([.hour, .minute], from: time), repeats: true ) let request = UNNotificationRequest(identifier: habit.id.uuidString, content: content, trigger: trigger) try await center.add(request) } - 7
Step 7 — Paywall gating + ship
Free tier: 3 habits, basic theme. Premium: unlimited habits, all themes, widget customization, CSV export. RevenueCat handles the rest. Ship with included App Store assets.
if !subscription.isPremium && habits.count >= 3 { showPaywall = true; return }
Frequently Asked Questions
How long does it take to build a habit tracker app with SwiftUI?
Should I use SwiftData or Core Data for habit tracker storage?
How do I implement streaks in SwiftUI?
How do I schedule habit reminder push notifications?
What pricing model works for habit tracker apps?
Does The Swift Kit help with habit tracker apps?
Keep exploring
Ship your iOS app 10× faster
The Swift Kit gives you a production-ready SwiftUI boilerplate — design system, paywall, auth, AI, all pre-wired. $99 one-time.
Get The Swift Kit — $99One-time purchase · Lifetime updates · 14-day refund