Skip to content

Repository files navigation

PatternKit — Clean Architecture + Coordinator (UIKit)

Part of PatternKit, a side-by-side reference codebase where the same small Tasks CRUD app is implemented once per architecture pattern across iOS and Android. Every module ships identical behaviour — the same domain model, the same three screens, the same mock data layer — so the only thing that varies is the architecture itself.

This module is the Clean Architecture flavour on UIKit. It keeps MVVM in the presentation layer but interposes a domain layer of use cases between the view models and the data layer, and drives navigation with a classic delegate-based Coordinator owning a UINavigationController. It's the imperative counterpart to the SwiftUI Clean module — same layering, same use cases, rendered the way pre-SwiftUI production apps are built — and the direct Clean-axis sibling of the MVVM-UIKit module: same UI stack, but with the domain layer added.

Stack

  • Language: Swift
  • UI: UIKit (UITableView, view controllers, programmatic flow)
  • Architecture: Clean Architecture (Domain / Data / Presentation) with MVVM presentation + Coordinator
  • DI: Manual constructor injection (AppContainer)
  • Navigation: Coordinator (AppCoordinator owning a UINavigationController, per-screen delegate protocols)
  • Deployment target: iOS 17.0 minimum (built against the iOS 26.5 SDK)
  • Bundle ID: com.preetanshumishra.PatternKitiOSCleanUIKit

The Tasks feature

A single-user task list. One entity (TaskItem: title, optional notes, optional due date, priority, completion). Three screens:

  1. List (TaskListViewController) — filter (All / Active / Completed), sort by due date or priority, swipe-to-delete, + bar button to create.
  2. Detail (TaskDetailViewController) — read-only fields, toggle completion, edit, delete.
  3. Form (TaskFormViewController) — create or edit (mode-driven), title validation (≤ 80 chars), due-date validation (not in the past), 600 ms mock async save.

Data comes from MockTaskRepository — an in-memory store seeded with ~12 tasks, with configurable artificial latency and failure rate. No real network, no local persistence — intentionally, so the architecture stays the focus.

The domain layer (what makes this "Clean")

Five single-responsibility use cases sit between the presentation and data layers:

  • FetchTasksUseCase, CreateTaskUseCase, UpdateTaskUseCase, DeleteTaskUseCase, ToggleTaskCompletionUseCase

Each depends only on the TaskRepository protocol (declared in Domain/). View models compose use cases rather than touching the repository directly — the one structural difference from the MVVM-UIKit module. The completion-toggle and updatedAt-stamp rules that lived in the MVVM-UIKit view models now live in their use cases, reusable and unit-testable in isolation from any UI.

UIKit-stack specifics

  • AppDelegate + SceneDelegate — classic app lifecycle and window setup, no SwiftUI App. The scene builds and retains the AppCoordinator.
  • UITableView + TaskCell — list rendering with a custom cell.
  • View models (TaskListViewModel, TaskFormViewModel) expose state and bind to view controllers via closures/callbacks — the manual binding that SwiftUI gives you for free.

Dependency injection

Manual constructor-based DI, no third-party container. AppContainer builds the repository, wires it into the use cases, and injects those into the view models, which are in turn handed to their view controllers.

Navigation (Coordinator)

This is the classic, delegate-based Coordinator. Each screen declares a thin protocol of the navigation events it can emit (TaskListCoordinating, TaskDetailCoordinating, TaskFormCoordinating); the view controllers hold a weak reference to one of those protocols and fire semantic events (taskListDidSelect, taskDetailDidRequestEdit, taskFormDidSave, …) rather than pushing, presenting, or popping anything themselves. AppCoordinator owns the UINavigationController and the shared list view model, conforms to all three protocols, and decides every transition in one place. The view controllers therefore depend only on their protocol — never on each other or on the navigation controller.

Project layout

PatternKitiOSCleanUIKit/
├── Domain/
│   ├── TaskItem, Priority, TaskFilter, TaskSort
│   ├── TaskRepository.swift          # protocol
│   └── UseCases/                      # five use cases
├── Data/              # MockTaskRepository, seed data
├── ViewModels/        # TaskListViewModel, TaskFormViewModel
├── ViewControllers/   # List / Detail / Form controllers + TaskCell
├── Coordinator/       # AppCoordinator + per-screen coordinating protocols
├── AppContainer.swift
├── AppDelegate.swift
└── SceneDelegate.swift

Build & run

Open PatternKitiOSCleanUIKit.xcodeproj in Xcode, then ⌘R to build and run (⌘U for tests).

About

Tasks CRUD reference app built with UIKit and Clean Architecture (Domain/Data/Presentation) with a Coordinator. Part of PatternKit, a side-by-side comparison of mobile architecture patterns.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages