An opinionated iOS productivity app built around one idea: three tasks a day, nothing more.
Daily Focus is a native iOS application that enforces a hard limit of three focus tasks per day. The constraint is intentional — the app is designed to push you to be selective about what actually matters, rather than maintaining an endless to-do list. When your three tasks are done, you're done.
Onboarding Focus – Dark Focus – Light Add Task
Calendar – Dark Calendar – Light Task Detail
- 3-task daily limit — enforced at every entry point with a clear error message if you hit the ceiling
- Task states — each task is automatically classified as In Focus (the first uncompleted task), Next Up, or Completed, with distinct visual styling for each
- Priority sorting — tasks are sorted High → Medium → Low; tasks of equal priority keep their original insertion order (stable sort)
- Completion toggle — tap the action icon on any card to mark it complete / incomplete
- Swipe to delete — standard iOS trailing swipe on any task card
- Carried-over tag — tasks flagged as carried over from a previous day show an orange "CARRIED OVER" badge
- Overview card — animated circular progress ring in the header shows how many of today's tasks are done, with a live "X of Y" count
- Completion celebration — full-screen confetti animation (DotLottie) fires when all tasks are marked complete; plays only once per day
- Empty state — first-launch onboarding screen with brand copy, three bento-style step cards, and a gradient CTA button
- Add task sheet — centred modal overlay with a text field, Low / Medium / High priority selector, All Day toggle, and start / end time pickers
- Monthly calendar grid — navigate by month; shows priority-coloured task stripes beneath each date
- Blue (high priority), amber (medium), green (low)
- Selected day rendered as a filled accent-coloured rounded square
- Today (when not selected) rendered with accent-coloured number text
- Month navigation — left/right chevron pill to step between months
- Month header — large month name with year and "X Tasks Completed" subtitle, computed live from the displayed month's data
- Day agenda — tapping any date updates the "Focus for {Month Day}" panel below the grid, listing that day's tasks with a left priority border, icon box, title, time range, and a circle completion toggle
- Add task FAB — floating "+" button appears when the selected day has fewer than 3 tasks
- Swipe to delete on any calendar task card
- Task detail sheet — tap any card to edit title, priority, all-day flag, and start/end times
- Light & Dark mode — entire colour system defined as adaptive
UIColorinstances inAppTheme; no hard-coded colours anywhere in UI code - Appearance toggle — side drawer lets you switch between light and dark regardless of the system setting, persisted across launches
- Side tools drawer — slides in from either screen edge via a drag gesture; remembers which edge and vertical position between sessions; contains the appearance toggle and a daily reset button
- Auto midnight refresh — listens for
UIApplication.significantTimeChangeNotificationand refreshes both tabs when the calendar day rolls over - Data migration —
PersistenceManagerautomatically upgrades the old flat-array storage format to the current day-keyed dictionary format, and fills in default values for any missing fields from earlier model versions
| Layer | Technology |
|---|---|
| Language | Swift 5 |
| UI Framework | UIKit — 100% programmatic Auto Layout, zero Storyboards or XIBs |
| Reactive bindings | Combine (@Published + sink for ViewModel → ViewController) |
| Animation | Core Animation (CAShapeLayer, CAGradientLayer, CABasicAnimation) |
| Lottie animation | DotLottie iOS SDK |
| Persistence | UserDefaults + JSONEncoder / JSONDecoder |
| Minimum deployment | iOS 16 |
| Architecture | MVVM — TaskViewModel owns data and business logic; ViewControllers own layout and interaction |
Building this project end-to-end without Interface Builder was a genuine deep dive into UIKit. Key takeaways:
- Programmatic Auto Layout at scale — chaining complex constraint trees across custom
UIViewsubclasses, usingintrinsicContentSize,contentCompressionResistancePriority, andUIStackViewaxes to avoid ambiguity without magic numbers - CALayer-based custom drawing — building the circular progress ring entirely with
CAShapeLayer+CABasicAnimation, and compositingCAGradientLayeronto aUIButtonwith correct frame lifecycle (layoutSubviewsfor gradient resizing) - Combine in practice — connecting a
@Publishedarray in the ViewModel to the ViewController withsink, handling thread dispatch back to main, and managingAnyCancellablelifetime withSet<AnyCancellable> - Stable sort in Swift — Swift's
Array.sortedis stable (documented since Swift 5), making it safe to sort by priority while preserving relative insertion order within priority groups - UICollectionView for a calendar grid — driving a 7-column month grid with
UICollectionViewFlowLayout, computing leading padding cells for the first weekday, and dynamically updating the height constraint as the number of rows changes between 5 and 6 - Screen-edge pan gestures — combining
UIScreenEdgePanGestureRecognizerwithUIPanGestureRecognizerfor the side drawer, with axis-locking logic to distinguish a horizontal open gesture from a vertical reposition drag - UserDefaults data migration — writing forward-compatible
Decodableinit(from:)withdecodeIfPresentso existing users' data survives model changes without a separate migration script - DotLottie integration — embedding a
.lottiebundle file and loading it at runtime via the DotLottie Swift SDK, with proper play/pause lifecycle tied to the view hierarchy - Semantic colour system — defining every colour as a
UIColor { traitCollection in ... }closure in a singleAppThemeenum so every view automatically responds to dark/light mode transitions
Daily Focus/
├── App/
│ ├── AppDelegate.swift # UIApplicationDelegate, appearance applied at launch
│ └── SceneDelegate.swift # UIWindowScene setup
│
├── Model/
│ └── FocusTask.swift # FocusTask struct, TaskPriority enum, TaskFormPayload
│
├── ViewModel/
│ ├── TaskViewModel.swift # Business logic, Combine @Published tasks, stable sort
│ └── TaskError.swift # LocalizedError cases: limitReached, emptyTitle
│
├── View/
│ ├── MainTabBarController.swift # Root tab bar (Focus + Calendar tabs)
│ │
│ ├── ── Focus Tab ─────────────────────────────────────────────────────────
│ ├── TaskListViewController.swift # Main focus screen; Combine binding to ViewModel
│ ├── TaskListHeaderView.swift # Overview card + CircularProgressView
│ ├── TaskListFooterView.swift # "Add New Focus" pill button + message label
│ ├── TaskCardCell.swift # UITableViewCell; TaskDisplayState styling
│ ├── EmptyStateView.swift # First-launch onboarding / empty state
│ ├── CompletionCelebrationView.swift # DotLottie confetti overlay
│ │
│ ├── ── Calendar Tab ──────────────────────────────────────────────────────
│ ├── CalendarViewController.swift # Month + day agenda, FAB, data persistence
│ ├── MonthCalendarView.swift # UICollectionView month grid + Stitch header
│ ├── CalendarEventCell.swift # Stitch-style calendar task card
│ ├── TaskCalendarDetailViewController.swift # Edit/delete sheet for calendar tasks
│ │
│ ├── ── Shared ────────────────────────────────────────────────────────────
│ ├── AddTaskSheetView.swift # Centred modal overlay for adding tasks
│ ├── TaskFormView.swift # Reusable form (title, priority, timing)
│ └── SideToolsDrawerView.swift # Edge-pinned drawer (appearance toggle + reset)
│
├── Service/
│ ├── PersistenceManager.swift # UserDefaults JSON read/write + migration
│ └── PersistenceManagerProtocol.swift # Protocol for testability
│
├── Theme/
│ ├── AppTheme.swift # All adaptive UIColor tokens
│ └── AppearanceManager.swift # Light/dark preference, persisted, broadcast via NotificationCenter
│
├── Utilities/
│ └── DayKey.swift # "yyyy-MM-dd" string ↔ Date helpers
│
├── Resources/
│ └── Confetti.lottie # DotLottie animation file
│
└── SCREENS/ # App screenshots and animation preview
├── 01-onboarding.png
├── 02-add-task.png
├── 03-focus-dark.png
├── 04-focus-light.png
├── 05-task-detail.png
├── 06-calendar-light.png
└── 08-confetti.gif
└── 07-calendar-dark.png
- Xcode 15 or later
- iOS 16+ device or simulator
- Swift Package Manager (dependencies resolve automatically on first build)
git clone https://github.com/<your-username>/daily-focus.git
cd daily-focus
open "Daily Focus.xcodeproj"Select a simulator or connected device and press ⌘R.
No API keys, no backend, no configuration needed. All data lives in the app's local
UserDefaultssandbox.
| Package | Purpose |
|---|---|
| dotlottie-ios | Confetti animation on all-tasks-complete |
Managed via Swift Package Manager — Xcode resolves it automatically.
This project is feature-complete from the author's perspective. I am no longer actively developing or adding new features. The codebase is stable and all the functionality I set out to build is done.
That said, contributions are very welcome! If you spot a bug, have an idea, or want to extend the app, feel free to open a pull request or an issue. Some directions that could be interesting:
- iCloud / CloudKit sync
- Home screen widget
- Local notifications / reminders for scheduled tasks
- Haptic feedback on completion
- iPad adaptive layout
- Unit & UI tests for
TaskViewModelandPersistenceManager - Accessibility improvements (VoiceOver labels, Dynamic Type support)
- Fork the repository
- Create a feature branch:
git checkout -b feature/your-idea - Commit your changes:
git commit -m 'Add your feature' - Push to the branch:
git push origin feature/your-idea - Open a pull request
Please keep PRs focused — one feature or fix per PR makes review much easier.
This project is available under the MIT License.
Built with UIKit, Combine, and the conviction that three tasks a day is enough.







