VIPER Architecture Setup for iOS App
VIPER — most detailed of iOS architectures. View, Interactor, Presenter, Entity, Router — five components per screen. In small teams this meets skepticism: many files, many protocols, much code. In teams of 5+ working on one UIKit application, VIPER becomes competitive advantage: isolated modules don't conflict in git, Interactor tests without UI, Router encapsulates navigation.
VIPER module anatomy
One screen = one VIPER module. For profile screen:
ProfileModule/
ProfileView.swift // UIViewController, implements ProfileViewProtocol
ProfilePresenter.swift // presentation logic, implements ProfilePresenterProtocol
ProfileInteractor.swift // business logic and data work
ProfileRouter.swift // navigation, implements ProfileRouterProtocol
ProfileAssembly.swift // factory, assembles module and injects dependencies
Protocols/
ProfileProtocols.swift // all module protocols in one file
Protocols — VIPER foundation:
// View ← Presenter
protocol ProfileViewProtocol: AnyObject {
func displayUser(_ viewModel: ProfileViewModel)
func displayError(_ message: String)
func setLoading(_ loading: Bool)
}
// Presenter ← View
protocol ProfileViewToPresenterProtocol: AnyObject {
func viewDidLoad()
func editButtonTapped()
func settingsTapped()
}
// Presenter ← Interactor
protocol ProfileInteractorOutputProtocol: AnyObject {
func didFetchUser(_ user: User)
func didFailFetchUser(_ error: Error)
}
// Interactor ← Presenter
protocol ProfileInteractorInputProtocol: AnyObject {
func fetchUser()
}
// Router ← Presenter
protocol ProfileRouterProtocol: AnyObject {
func navigateToEditProfile(user: User)
func navigateToSettings()
}
This much code. That's why VIPER uses Xcode templates or generators (Generamba, Vipergen) — one template, generate profile command creates all 7 files with base code.
Interactor — business logic heart
Interactor — only place where business logic lives. Doesn't know UIKit, doesn't know specific storage:
final class ProfileInteractor {
weak var presenter: ProfileInteractorOutputProtocol?
private let userRepository: UserRepositoryProtocol
init(userRepository: UserRepositoryProtocol) {
self.userRepository = userRepository
}
}
extension ProfileInteractor: ProfileInteractorInputProtocol {
func fetchUser() {
Task {
do {
let user = try await userRepository.fetchCurrentUser()
await MainActor.run {
presenter?.didFetchUser(user)
}
} catch {
await MainActor.run {
presenter?.didFailFetchUser(error)
}
}
}
}
}
Testing Interactor: mock UserRepositoryProtocol — clean Swift, XCTest without simulator. This why boilerplate cost justified.
Presenter: data transformation
Presenter receives domain Entity from Interactor and creates ViewModel for View:
extension ProfilePresenter: ProfileInteractorOutputProtocol {
func didFetchUser(_ user: User) {
let viewModel = ProfileViewModel(
displayName: "\(user.firstName) \(user.lastName)",
avatarURL: user.avatarURL,
memberSince: DateFormatter.mediumStyle.string(from: user.createdAt),
isVerified: user.verificationStatus == .verified
)
view?.displayUser(viewModel)
view?.setLoading(false)
}
}
ProfileViewModel — struct with UI data, not domain User. View gets ready strings, doesn't format.
Assembly: module assembly
All initialization and DI — in Assembly:
enum ProfileAssembly {
static func build(coordinator: AppCoordinator) -> UIViewController {
let interactor = ProfileInteractor(
userRepository: DI.resolve(UserRepositoryProtocol.self)
)
let router = ProfileRouter(coordinator: coordinator)
let presenter = ProfilePresenter(interactor: interactor, router: router)
let view = ProfileViewController()
view.presenter = presenter
presenter.view = view
interactor.presenter = presenter
return view
}
}
Entire module dependency graph visible in one file. Adding new dependency — only Assembly changes, rest isolated.
Code generators
Writing VIPER manually — too time-expensive. Tools:
-
Generamba — Ruby gem, templates via YAML, Xcode integration with
generamba gen ProfileModule viper - XcodeGen with custom templates
- Swift Package with Makefile — own generator based on Stencil templates
On projects with 30+ modules without generator VIPER becomes pain.
VIPER vs Clean Architecture + MVVM
VIPER organic for UIKit + Objective-C legacy or big teams with established processes. For new SwiftUI projects Clean Architecture + MVVM + @Observable — cleaner without boilerplate. For Flutter — BLoC with Clean Architecture closer to VIPER by ideology, but without UIKit dependencies.
VIPER choice justified if:
- Existing project already on VIPER (refactoring pointless)
- Team 5+ iOS developers, active parallel code
- High testability requirements per layer
What we set up
Design protocols → create Xcode template or configure Generamba → implement base VIPER module with tests as example → document team rules → optionally migrate priority MVC/MVP screens.
Work takes 3–5 days for new project including generator and docs. Cost calculated after analyzing module count and team composition.







