Skip to content

Example iOS app built with SwiftUI and Clean Architecture (MVVM). Focused on separation of concerns, dependency injection, and scalability, testability for real world production projects.

Notifications You must be signed in to change notification settings

sourov2008/SwiftUI-MVVM-Clean

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

24 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

SwiftUI MVVM Clean Architecture

  • Swift
  • Xcode
  • iOS
  • Architecture
  • Pattern
  • Principle
  • Principle

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.


🧠 Why This Project

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.

πŸ—οΈ Project Structure (recommended)

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.


πŸ“Έ How Data Flows (Runtime Data Flow)

View β†’ ViewModel β†’ UseCase β†’ Repository β†’ API/LocalData
↑                                               ↓
└───────────── Updates UI with new data β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Explanation:

  1. The View asks the ViewModel to do something (e.g., load users).
  2. The ViewModel calls a UseCase to run the business logic.
  3. The UseCase uses a Repository, which fetches data (API or local).
  4. Data flows back up and the View updates automatically.

βš™οΈ Dependency Injection (DI)

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).


🧩 Example Feature: β€œUsers List” (concept)

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.


πŸ§ͺ Testing (quick sample)

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


🧰 Tools Used

  • SwiftUI for UI
  • Combine / async-await for async updates
  • Swift Testing for unit tests

πŸš€ How to Run

  1. Clone the repo
    git clone https://github.com/sourov2008/SwiftUI-MVVM-Clean.git
  2. Open SwiftUIMVVMClean.xcodeproj in Xcode
  3. Press Run ▢️

πŸͺ„ Add Your Own Feature (Checklist)

Want to extend the app? Follow these simple steps πŸ‘‡

  1. Create an Entity

    struct Product: Identifiable {
        let id: Int
        let name: String
    }
  2. Define a Repository Protocol

    protocol ProductRepository {
        func fetchProducts() async throws -> [Product]
    }
  3. Implement the Repository

    final class ProductRepositoryImpl: ProductRepository {
        func fetchProducts() async throws -> [Product] {
            // Call API or return mock data
            return [Product(id: 1, name: "Sample")]
        }
    }
  4. 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()
        }
    }
  5. 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) }
        }
    }
  6. 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 🎯


πŸ€– AI Assistance

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.


❀️ Author

Sourob Datta
iOS Developer β€’ WatchOS developer. [email protected] GitHub Profile

About

Example iOS app built with SwiftUI and Clean Architecture (MVVM). Focused on separation of concerns, dependency injection, and scalability, testability for real world production projects.

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages