iOS-Clean-Architecture-MVVM: A Swift App Template with Layered Design
Template iOS app using Clean Architecture and MVVM. Includes DIContainer, FlowCoordinator, DTO, Response Caching and one of the views in SwiftUI
At a glance
- What is it?
- A template iOS project that enforces separation between domain logic, data access, and presentation using MVVM. Aimed at Swift developers who want a concrete starting structure for a clean-layered app rather than theoretical diagrams.
- Who is it for?
- This template suits iOS developers who want to start a new app with Clean Architecture already wired: the layers, DI container, flow coordinators, and tests are in place. It is not a good fit if you need a backend, authentication, or offline sync patterns; none of those are included.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository last received commits 81 days ago.
- What is it written in?
- Mainly Swift, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 27, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the Three-Layer Separation Enforces in Practice
The repository is organised around three layers that map closely to Uncle Bob's Clean Architecture. The Domain Layer contains Entities, Use Cases, and Repository interfaces. The Data Repositories Layer holds the concrete implementations of those interfaces plus the network client and the persistence database. The Presentation Layer applies MVVM: ViewModels sit between the Views and the domain Use Cases.
The critical constraint is the dependency direction. The Domain Layer must not import anything from the other two layers: no UIKit, no SwiftUI, no Codable mappings. That boundary keeps business rules testable without a running UI or a live network. The README states this explicitly: the Domain Layer must not include anything from other layers, such as Presentation (UIKit or SwiftUI) or Data Layer (Mapping Codable).
This means a ViewModel imports a Use Case interface from the domain, and the concrete Use Case imports a Repository interface, also from the domain. The actual database or network adapter is injected at startup through the DIContainer and never referenced by the ViewModel directly.
Setting Up and Running the Template
The repository requires Xcode 11.2.1 or later and Swift 5.0 or later. There is no package manager setup step: the project opens directly as an Xcode project.
git clone https://github.com/kudoleh/iOS-Clean-Architecture-MVVM.git
cd iOS-Clean-Architecture-MVVM
open ExampleMVVM.xcodeprojOnce open in Xcode, build and run the ExampleMVVM scheme. The app presents a movie search interface: type a title in the search bar and tap the search button. The app makes two network calls, one for movie results and one for poster images. Every successful query is stored persistently so past searches appear in a list without a new network request.
The domain is movies, and the README notes the project is intended to be used as a template by replacing the item name "Movie" throughout. That substitution is a manual find-and-replace across filenames, type names, and strings, not a generator script.
AppDIContainer, Flow Coordinators, and DTOs
Dependency injection is handled through AppDIContainer, defined in ExampleMVVM/Application/DIContainer/AppDIContainer.swift. The container constructs the concrete repositories and network services at application startup, then passes them down to scene-level containers that build ViewModels. This avoids a global service locator: each ViewModel receives its dependencies through initialiser injection rather than reaching into a singleton.
Navigation is managed by Flow Coordinators. MoviesSearchFlowCoordinator, for example, owns the navigation controller and decides when to push a movie detail view or present the search results. The coordinator pattern keeps navigation logic out of ViewModels, which would otherwise need to import UIKit to call present or push.
Data Transfer Objects (DTOs) appear in the Data layer. MoviesResponseDTO+Mapping.swift receives the raw JSON from the network and maps it into domain Entities. This isolates the domain from any changes in the API response shape: if the API renames a field, only the DTO mapping changes, not the Use Cases or the ViewModels.
Data Binding with Observable and No Third-Party Libraries
The Presentation Layer binds ViewModels to Views using a hand-rolled Observable type defined in ExampleMVVM/Presentation/Utils/Observable.swift. Observable is a generic wrapper that calls a closure whenever its value changes. A ViewModel exposes properties as Observable, and a View subscribes in viewDidLoad by assigning to the observe closure.
This is a deliberate design choice to avoid Combine, RxSwift, or any reactive library dependency. The trade-off is that Observable is simpler and easier to understand but does not compose operators the way Combine or RxSwift does. For the scope of the template, which covers list display, search, and detail views, the simpler approach is sufficient. A team that already uses Combine would likely swap Observable out early.
Response caching sits in DefaultMoviesRepository. The repository checks a local store before making a network request, and on a successful response it writes the result back. The README does not document the eviction policy or the maximum cache size, so those limits are not covered here.
SwiftUI and UIKit Views Sharing One ViewModel
The template includes two implementations of the MoviesQueriesList view: one in UIKit and one in SwiftUI. Both use the same MoviesQueryListViewModel without modification. The SwiftUI version is located at ExampleMVVM/Presentation/MoviesScene/MoviesQueriesList/View/SwiftUI/MoviesQueryListView.swift.
This demonstrates the architectural claim that the Presentation Layer is UI-framework-agnostic: because ViewModels do not import UIKit or SwiftUI directly, swapping the view layer does not require touching the ViewModel or anything beneath it. The README requires at least Xcode 11 for the SwiftUI example to compile, since SwiftUI was introduced in that version.
In practice, mixing UIKit and SwiftUI hosting in one app requires UIHostingController, which is handled in the UIKit view hierarchy. Teams targeting older OS versions that do not support SwiftUI will simply use the UIKit view and ignore the SwiftUI file.
Unit Tests Across All Three Layers
The ExampleMVVMTests target includes unit tests for Use Cases in the Domain Layer, ViewModels in the Presentation Layer, and NetworkService in the Infrastructure Layer. These three test targets correspond directly to the three categories of logic that should be testable in isolation.
The CI pipeline is configured in .travis.yml using Travis CI and Fastlane. The Gemfile and Gemfile.lock in the repository root manage the Fastlane Ruby gem dependency. A team forking this template would need to update the Travis CI configuration with their own repository slug and, if they use a paid API, their own secrets.
Error handling is demonstrated in two places: in MoviesListViewModel for presentation-layer errors and in NetworkService for infrastructure-layer errors. These serve as examples of where to put error state rather than comprehensive patterns for every failure case.
Where the Template Has Real Gaps
The app requires a live network connection to an external movie API. The README does not document which API or how to configure an API key. A developer cloning the repository will encounter network failures until they find and set the endpoint configuration. This is a significant onboarding gap for a template intended for reuse.
The template does not cover authentication, push notifications, deep linking, or background tasks. It is also Storyboard-based for most views, which is a constraint some teams have moved away from. A companion repository, iOS-Clean-Architecture-MVVM-Views-In-Code, uses the same architecture but writes all views in code with UITableViewDiffableDataSource instead of Storyboards.
The observable binding pattern used here is not Combine and does not integrate with async/await in Swift concurrency. A team working on iOS 15+ targets would likely want to replace the Observable wrapper with a Combine publisher or an @Published property, which would require touching every ViewModel that exposes observable state.
Comparison with VIPER
VIPER is another layered iOS architecture that splits the presentation concern into five components: View, Interactor, Presenter, Entity, and Router. Compared to this template, VIPER makes the Router a first-class object responsible for navigation, similar to the Flow Coordinator here, but the Presenter and Interactor roles differ from ViewModel and Use Case in subtle ways: VIPER's Interactor handles business logic and has no direct access to the View, while MVVM's ViewModel holds state and binds directly to the View.
The Clean Architecture MVVM template here keeps fewer moving parts per screen: a ViewModel, a Use Case, and a Repository interface. VIPER typically introduces a Presenter layer between the View and the business logic, which adds an indirection step. Neither pattern is wrong, but the MVVM template in this repository has a shorter onboarding path for developers already familiar with iOS ViewModel patterns from SwiftUI's own design.
Editorial conclusion
This template suits iOS developers who want to start a new app with Clean Architecture already wired: the layers, DI container, flow coordinators, and tests are in place. It is not a good fit if you need a backend, authentication, or offline sync patterns; none of those are included. Before adopting it, verify that the TMDB API key requirement fits your project, and read through AppDIContainer.swift to understand what you will need to replace when you substitute your own domain.
Frequently asked questions
What is the difference between MVVM and clean architecture?
MVVM is a presentation pattern that organises the UI layer into Models, Views, and ViewModels. Clean Architecture is a broader layering strategy that separates the entire application into domain, data, and presentation layers. This repository uses Clean Architecture to define layer boundaries and then applies MVVM within the presentation layer.
What is clean architecture in iOS?
In this repository, clean architecture means the Domain Layer holds Use Cases and Repository interfaces with no iOS framework imports, the Data Layer provides concrete implementations, and the Presentation Layer uses MVVM with ViewModels that depend only on the domain interfaces.
What is MVVM architecture?
MVVM (Model-View-ViewModel) is a presentation pattern where the ViewModel holds view state and business logic for one screen, the View observes that state and renders it, and the Model supplies the underlying data. In this template, ViewModels call Use Cases from the Domain Layer rather than accessing data directly.
What is the iOS architecture used by Apple?
The README does not address Apple's own architecture recommendations. Apple's documentation historically favored MVC for UIKit apps, but this template specifically demonstrates Clean Architecture and MVVM as an alternative that isolates domain logic from both the UI framework and the network layer.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/kudoleh-ios-clean-architecture-mvvm)