Welcome π
This project demonstrates a scalable, maintainable, and testable iOS app structure built with SwiftUI, following the MVVM pattern combined with Clean Architecture, Object-Oriented Programming (OOP), and SOLID principles.
Itβs written in simple language and a clear folder structure, so beginners can learn how to organize bigger SwiftUI projects the right way.
This project is designed as a reference architecture for iOS developers who want to build real world iOS apps that remain clean, testable, and scalable over time.
It follows a structured approach using MVVM, Clean Architecture, OOP, and SOLID principles to ensure long-term maintainability and clarity.
This means:
- β Easier to test β business logic can be tested without running the app.
- β Easier to maintain β changes in one layer rarely break others.
- β Highly scalable β new features can be added safely as your app grows.
- β Great for team projects β multiple developers can work in parallel on different layers (UI, domain, or data) without conflicts.
- β Ideal for long term apps β perfect for products that will evolve over the years.
- β Reusable logic - the same use cases can power multiple UIs (SwiftUI, UIKit, watchOS, macOS).
- β Better separation of concerns β UI code stays focused on display, not logic.
- β Plug and play dependencies β easily swap APIs, databases, or mock services.
- β Improved readability β new team members can quickly understand the structure.
- β Future proof design β your core logic survives framework changes.
SwiftUI-MVVM-Clean
β
βββ Domain // Domain layer should be completely independent of frameworks and third-party libraries; even small utility ones.
β β Prohibited: like Alamofire, Moya, Combine, Realm, CoreData, SwiftUI, UIKit....
β βββ Entities // Core data models used across the app
β βββ UseCases // Business rules (pure Swift)
β βββ Repositories // Protocols that describe data access
β
βββ Data
β βββ Remote // API / networking / mock data
β βββ Local // Local DB or UserDefaults (if used)
β βββ RepositoryImpl // Classes that implement Repository protocols
β
βββ Presentation
β βββ ViewModels // Handles data for SwiftUI views
β βββ Views // SwiftUI screens & components
β
βββ App
β βββ DI // Dependency Injection container / factories
β βββ SwiftUIMVVMCleanApp.swift // Entry point
β
βββ Tests
βββ DomainTests
βββ DataTests
βββ PresentationTests
Each folder has a clear purpose; this makes the app easy to navigate and scale.
Tip: If your current repo layout is different, you can still follow the same layer ideas.
View β ViewModel β UseCase β Repository β API/LocalData
β β
ββββββββββββββ Updates UI with new data βββββββββ
Explanation:
- The View asks the ViewModel to do something (e.g., load users).
- The ViewModel calls a UseCase to run the business logic.
- The UseCase uses a Repository, which fetches data (API or local).
- Data flows back up and the View updates automatically.
All parts are connected using protocols.
At app start, the DI Container decides which real or mock implementation to use.
Example:
let userRepo = UserRepositoryImpl(apiService: ApiService())
let getUserUseCase = GetUserUseCase(repository: userRepo)
let viewModel = UserViewModel(useCase: getUserUseCase)This makes it easy to swap components (e.g., use a mock repository in tests).
| Layer | What it does |
|---|---|
Entity |
Defines a User model. |
Repository |
Declares fetchUsers() protocol. |
RepositoryImpl |
Calls an API to get users. |
UseCase |
Uses the repository to get data for the ViewModel. |
ViewModel |
Calls the use case and exposes state for the View. |
View |
Displays the list of users in SwiftUI. |
Simple β Clean β Testable.
Because every part uses protocols, you can test easily:
class MockUserRepository: UserRepository {
func fetchUsers() async throws -> [User] {
[User(id: 1, name: "Test User")]
}
}
func testUserUseCase() async throws {
let mockRepo = MockUserRepository()
let useCase = GetUserUseCase(repository: mockRepo)
let users = try await useCase.execute()
XCTAssertEqual(users.first?.name, "Test User")
}β
No real API
β
Fast & reliable tests
- SwiftUI for UI
- Combine / async-await for async updates
- Swift Testing for unit tests
- Clone the repo
git clone https://github.com/sourov2008/SwiftUI-MVVM-Clean.git
- Open
SwiftUIMVVMClean.xcodeprojin Xcode - Press Run
βΆοΈ
Want to extend the app? Follow these simple steps π
-
Create an Entity
struct Product: Identifiable { let id: Int let name: String }
-
Define a Repository Protocol
protocol ProductRepository { func fetchProducts() async throws -> [Product] }
-
Implement the Repository
final class ProductRepositoryImpl: ProductRepository { func fetchProducts() async throws -> [Product] { // Call API or return mock data return [Product(id: 1, name: "Sample")] } }
-
Add a UseCase
final class GetProductsUseCase { private let repository: ProductRepository init(repository: ProductRepository) { self.repository = repository } func execute() async throws -> [Product] { try await repository.fetchProducts() } }
-
Create a ViewModel
@MainActor final class ProductViewModel: ObservableObject { @Published var products: [Product] = [] private let useCase: GetProductsUseCase init(useCase: GetProductsUseCase) { self.useCase = useCase } func loadProducts() async { do { products = try await useCase.execute() } catch { print(error) } } }
-
Add a View
struct ProductListView: View { @StateObject var viewModel: ProductViewModel var body: some View { List(viewModel.products) { product in Text(product.name) } .task { await viewModel.loadProducts() } } }
Now youβve added a full new feature β clean and testable π―
This project was created by Sourob Datta with support from AI tools (ChatGPT) to organize structure, documentation, and examples.
The goal was to make Clean Architecture easy to understand for beginners learning SwiftUI + MVVM.
Sourob Datta
iOS Developer β’ WatchOS developer.
[email protected]
GitHub Profile