A simple Todo application built with Swift and SwiftUI as part of an ongoing effort to refresh and deepen iOS development skills. This is a learning-focused project and should be treated as a practice app, not production-ready software.
This project serves as a hands-on exercise to build familiarity with modern iOS development patterns including SwiftUI declarative UI, SwiftData persistence, and the MVVM architecture.
| Technology | Role |
|---|---|
| Swift 5.9+ | Primary language |
| SwiftUI | Declarative UI framework |
| SwiftData | Local persistence (iOS 17+) |
| MVVM | Architecture pattern |
| Xcode 15+ | IDE |
- Add and delete todo items
- Mark items as complete with a strikethrough indicator
- Data persisted locally across app launches via SwiftData
TodoApp/
├── TodoAppApp.swift # App entry point, SwiftData container setup
├── ContentView.swift # Main screen
├── TodoRowView.swift # Single todo row component
├── Models/
│ └── TodoItem.swift # SwiftData model
└── ViewModels/
└── TodoViewModel.swift # Business logic and data operations
The app follows the MVVM (Model-View-ViewModel) pattern:
- Model —
TodoItemis a@Modelclass managed by SwiftData, acting as the data entity - ViewModel —
TodoViewModelowns the business logic (add, delete, toggle) and exposes state to views via@Observable - View — SwiftUI views are kept thin, delegating all logic to the ViewModel
As the project evolves, the following patterns are planned:
- Repository layer — abstract data access behind a
TodoRepositoryprotocol, decoupling the ViewModel from SwiftData directly. - Dependency injection — pass dependencies (context, repository) through initializers rather than pulling them from the environment, improving testability and separation of concerns
- Unit tests — test ViewModel logic in isolation using an in-memory SwiftData store
This app is intended to grow as a practice vehicle. Planned additions include:
- Edit todos — tap a row to open a detail view and rename it, introducing
NavigationLinkand passing data between views - Filter todos — toggle between All / Active / Completed using
@Statefor selection andPredicateinFetchDescriptor -
@Querymacro — replace the manualfetchItems()call with SwiftData's@Queryproperty wrapper for automatic list refresh and cleaner view code
- Requires iOS 17+ due to SwiftData and
@Observable - This project evolves incrementally — commits reflect learning progression, not finished features
This project is licensed under the MIT License. See LICENSE for details.