Thursday, April 30, 2026
HomeiOS DevelopmentVIPER finest practices for iOS builders

VIPER finest practices for iOS builders

[ad_1]

On this tutorial I’ll present you an entire information about easy methods to construct a VIPER primarily based iOS utility, written solely in Swift.

VIPER

This put up is a bit of bit outdated, please anticipate a brand new model coming quickly…

Getting began with VIPER

Initially, it’s best to learn my earlier (extra theoretical) article in regards to the VIPER structure itself. It is a fairly respectable writing explaining all of the VIPER parts and reminiscence administration. I’ve additionally polished it a bit of bit, final week. ⭐️

The issue with that article nevertheless was that I have never present you the actual deal, aka. the Swift code for implementing VIPER. Now after a full yr of tasks utilizing this structure I can lastly share all my finest practices with you.

So, let’s begin by making a model new Xcode mission, use the only view app template, title the mission (VIPER finest practices), use Swift and now you are able to take the subsequent step of creating an superior “enterprise grade” iOS app.


Producing VIPER modules

Lesson 1: by no means create a module by hand, all the time use a code generator, as a result of it is a repetative process, it is fuckin’ boring plus it’s best to deal with extra essential issues than making boilerplate code. You need to use my light-weight module generator known as:

VIPERA

Simply obtain or clone the repository from github. You’ll be able to set up the binary software by working swift run set up --with-templates. This can set up the vipera app beneath /usr/native/bin/ and the essential templates beneath the ~/.vipera listing. You need to use your individual templates too, however for now I will work with the default one. 🔨

I often begin with a module known as Principal that is the foundation view of the appliance. You’ll be able to generate it by calling vipera Principal within the mission listing, so the generator can use the right mission title for the header feedback contained in the template recordsdata.

Clear up the mission construction a bit of bit, by making use of my conventions for Xcode, which means that sources goes to an Property folder, and all of the Swift recordsdata into the Sources listing. These days I additionally change the AppDelegate.swift file, and I make a separate extension for the UIApplicationDelegate protocol.

Create a Modules group (with a bodily folder too) beneath the Sources listing and transfer the newly generated Principal module beneath that group. Now repair the mission points, by deciding on the Data.plist file from the Property folder for the present goal. Additionally do take away the Principal Interface, and after which you can safely delete the Principal.storyboard and the ViewController.swift recordsdata, as a result of we’re not going to wish them in any respect.

Contained in the AppDelegate.swift file, it’s important to set the Principal module’s view controller as the foundation view controller, so it ought to look considerably like this:

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder {

    var window: UIWindow?
}

extension AppDelegate: UIApplicationDelegate {

    func utility(_ utility: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        self.window = UIWindow(body: UIScreen.fundamental.bounds)
        self.window?.rootViewController = MainModule().buildDefault()
        self.window?.makeKeyAndVisible()

        return true
    }
}

Congratulations, you have created your very first VIPER module! 🎉


UITabBarController & VIPER

I’ve an excellent easy resolution for utilizing a tab bar controller in a VIPER module. First let’s generate a couple of new modules, these are going to be the tabs. I’ll use the JSONPlaceholder service, so we could say a separate tab for every of those sources: posts, albums, images, todos (with the identical module title). Generate all of them, and transfer them into the modules folder.

Now, let’s generate another module known as Residence. This can implement our tab bar controller view. If you would like you need to use the Principal module for this goal, however I wish to preserve that for animation functions, to have a neat transition between the loading display screen and my Residence module (all of it relies on your wants).

So the primary logic that we’ll implement is that this: the primary view will notify the presenter in regards to the viewDidAppear occasion, and the presenter will ask the router to show the Residence module. The Residence module’s view will probably be a subclass of a UITabBarController, it’s going to additionally notify it is presenter about viewDidLoad, and the presenter will ask for the right tabs, through the use of its router.

Right here is the code, with out the interfaces:

class MainDefaultView: UIViewController {

    var presenter: MainPresenter?

    override func viewDidAppear(_ animated: Bool) {
        tremendous.viewDidAppear(animated)

        self.presenter?.viewDidAppear()
    }
}

extension MainDefaultPresenter: MainPresenter {

    func viewDidAppear() {
        self.router?.showHome()
    }
}

extension MainDefaultRouter: MainRouter {

    func showHome() {
        let viewController = HomeModule().buildDefault()
        self.viewController?.current(viewController, animated: true, completion: nil)
    }
}


extension HomeDefaultView: HomeView {

    func show(_ viewControllers: [UIViewController]) {
        self.viewControllers = viewControllers
    }
}



extension HomeDefaultPresenter: HomePresenter {

    func setupViewControllers() {
        guard let controllers = self.router?.getViewControllers() else {
            return
        }
        self.view?.show(controllers)
    }

}

extension HomeDefaultRouter: HomeRouter {

    func getViewControllers() -> [UIViewController] {
        return [
            PostsModule().buildDefault(),
            AlbumsModule().buildDefault(),
            PhotosModule().buildDefault(),
            TodosModule().buildDefault(),
        ].map { UINavigationController(rootViewController: $0) }
    }
}

class HomeModule {

    func buildDefault() -> UIViewController {
        

        presenter.setupViewControllers()

        return view
    }
}

There may be one extra line contained in the Residence module builder perform that triggers the presenter to setup correct view controllers. That is simply because the UITabBarController viewDidLoad methodology will get known as earlier than the init course of finishes. This behaviour is kind of undocumented however I assume it is an UIKit hack in an effort to preserve the view references (or only a easy bug… is anybody from Apple right here?). 😊

Anyway, now you’ve a correct tab bar contained in the mission built-in as a VIPER module. It is time to get some information from the server and right here comes one other essential lesson: not all the things is a VIPER module.


Providers and entities

As you would possibly seen there is no such thing as a such factor as an Entity inside my modules. I often wrap APIs, CoreData and plenty of extra information suppliers as a service. This fashion, all of the associated entities could be abstracted away, so the service could be simply changed (with a mock for instance) and all my interactors can use the service via the protocol definition with out realizing the underlying implementation.

One other factor is that I all the time use my promise library if I’ve to cope with async code. The explanation behind it’s fairly easy: it is far more elegant than utilizing callbacks and optionally available outcome parts. You need to be taught guarantees too. So right here is a few a part of my service implementation across the JSONPlaceholder API:

protocol Api {

    func posts() -> Promise<[Post]>
    func feedback(for put up: Publish) -> Promise<[Comment]>
    func albums() -> Promise<[Album]>
    func images(for album: Album) -> Promise<[Photo]>
    func todos() -> Promise<[Todo]>
}



struct Publish: Codable {

    let id: Int
    let title: String
    let physique: String
}



class JSONPlaceholderService {

    var baseUrl = URL(string: "https://jsonplaceholder.typicode.com/")!

    enum Error: LocalizedError {
        case invalidStatusCode
        case emptyData
    }

    non-public func request<T>(path: String) -> Promise<T> the place T: Decodable {
        let promise = Promise<T>()
        let url = baseUrl.appendingPathComponent(path)
        print(url)
        URLSession.shared.dataTask(with: url) { information, response, error in
            if let error = error {
                promise.reject(error)
                return
            }
            guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
                promise.reject(Error.invalidStatusCode)
                return
            }
            guard let information = information else {
                promise.reject(Error.emptyData)
                return
            }
            do {
                let mannequin = strive JSONDecoder().decode(T.self, from: information)
                promise.fulfill(mannequin)
            }
            catch {
                promise.reject(error)
            }
        }.resume()
        return promise
    }
}

extension JSONPlaceholderService: Api {

    func posts() -> Promise<[Post]> {
        return self.request(path: "posts")
    }

    
}

Often I’ve a mock service implementation subsequent to this one, so I can simply take a look at out all the things I would like. How do I change between these companies? Properly, there’s a shared (singleton – do not hate me it is utterly effective 🤪) App class that I exploit principally for styling functions, however I additionally put the dependency injection (DI) associated code there too. This fashion I can cross round correct service objects for the VIPER modules.

class App {

    static let shared = App()

    non-public init() {

    }

    var apiService: Api {
        return JSONPlaceholderService()
    }
}



class PostsModule {

    func buildDefault() -> UIViewController {
        let view = PostsDefaultView()
        let interactor = PostsDefaultInteractor(apiService: App.shared.apiService)

        

        return view
    }
}



class PostsDefaultInteractor {

    weak var presenter: PostsPresenter?

    var apiService: Api

    init(apiService: Api) {
        self.apiService = apiService
    }
}

extension PostsDefaultInteractor: PostsInteractor {

    func posts() -> Promise<[Post]> {
        return self.apiService.posts()
    }

}

You are able to do this in a 100 different methods, however I presently choose this strategy. This fashion interactors can instantly name the service with some additional particulars, like filters, order, kind, and many others. Principally the service is only a excessive idea wrapper across the endpoint, and the interactor is creating the fine-tuned (higher) API for the presenter.


Making guarantees

Implementing the enterprise logic is the duty of the presenter. I all the time use guarantees so a fundamental presenter implementation that solely hundreds some content material asynchronously and shows the outcomes or the error (plus a loading indicator) is only a few strains lengthy. I am all the time attempting to implement the three fundamental UI stack parts (loading, information, error) through the use of the identical protocol naming conventions on the view. 😉

On the view aspect I am utilizing my good previous assortment view logic, which considerably reduces the quantity of code I’ve to put in writing. You’ll be able to go together with the standard method, implementing a couple of information supply & delegate methodology for a desk or assortment view is just not a lot code in any case. Right here is my view instance:

extension PostsDefaultPresenter: PostsPresenter {

    func viewDidLoad() {
        self.view?.displayLoading()
        self.interactor?.posts()
        .onSuccess(queue: .fundamental) { posts  in
            self.view?.show(posts)
        }
        .onFailure(queue: .fundamental) { error in
            self.view?.show(error)
        }
    }
}



class PostsDefaultView: CollectionViewController {

    var presenter: PostsPresenter?

    init() {
        tremendous.init(nibName: nil, bundle: nil)

        self.title = "Posts"
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been carried out")
    }

    override func viewDidLoad() {
        tremendous.viewDidLoad()

        self.presenter?.viewDidLoad()
    }
}

extension PostsDefaultView: PostsView {

    func displayLoading() {
        print("loading...")
    }

    func show(_ posts: [Post]) {
        let grid = Grid(columns: 1, margin: UIEdgeInsets(all: 8))

        self.supply = CollectionViewSource(grid: grid, sections: [
            CollectionViewSection(items: posts.map { PostViewModel($0) })
        ])
        self.collectionView.reloadData()
    }

    func show(_ error: Error) {
        print(error.localizedDescription)
    }
}

The cell and the ViewModel is outdoors the VIPER module, I are likely to dedicate an App folder for the customized utility particular views, extensions, view fashions, and many others.

class PostCell: CollectionViewCell {

    @IBOutlet weak var textLabel: UILabel!
}


class PostViewModel: CollectionViewViewModel<PostCell, Publish> {

    override func config(cell: PostCell, information: Publish, indexPath: IndexPath, grid: Grid) {
        cell.textLabel.textual content = information.title
    }

    override func dimension(information: Publish, indexPath: IndexPath, grid: Grid, view: UIView) -> CGSize {
        let width = grid.width(for: view, objects: grid.columns)
        return CGSize(width: width, top: 64)
    }
}

Nothing particular, if you would like to know extra about this assortment view structure, it’s best to learn my different tutorial about mastering assortment views.


Module communication

One other essential lesson is to discover ways to talk between two VIPER modules. Usually I am going with easy variables – and delegates if I’ve to ship again some kind of information to the unique module – that I cross round contained in the construct strategies. I’ll present you a very easy instance for this too.

class PostsDefaultRouter {

    weak var presenter: PostsPresenter?
    weak var viewController: UIViewController?
}

extension PostsDefaultRouter: PostsRouter {

    func showComments(for put up: Publish) {
        let viewController = PostDetailsModule().buildDefault(with: put up, delegate: self)
        self.viewController?.present(viewController, sender: nil)
    }
}

extension PostsDefaultRouter: PostDetailsModuleDelegate {

    func toggleBookmark(for put up: Publish) {
        self.presenter?.toggleBookmark(for: put up)
    }
}




protocol PostDetailsModuleDelegate: class {
    func toggleBookmark(for put up: Publish)
}

class PostDetailsModule {

    func buildDefault(with put up: Publish, delegate: PostDetailsModuleDelegate? = nil) -> UIViewController {
        let view = PostDetailsDefaultView()
        let interactor = PostDetailsDefaultInteractor(apiService: App.shared.apiService,
                                                      bookmarkService: App.shared.bookmarkService)
        let presenter = PostDetailsDefaultPresenter(put up: put up)

        

        return view
    }
}

class PostDetailsDefaultRouter {

    weak var presenter: PostDetailsPresenter?
    weak var viewController: UIViewController?
    weak var delegate: PostDetailsModuleDelegate?
}

extension PostDetailsDefaultRouter: PostDetailsRouter {

    func toggleBookmark(for put up: Publish) {
        self.delegate?.toggleBookmark(for: put up)
    }
}


class PostDetailsDefaultPresenter {

    var router: PostDetailsRouter?
    var interactor: PostDetailsInteractor?
    weak var view: PostDetailsView?

    let put up: Publish

    init(put up: Publish) {
        self.put up = put up
    }
}

extension PostDetailsDefaultPresenter: PostDetailsPresenter {

    func reload() {
        self.view?.setup(with: self.interactor!.bookmark(for: self.put up))

        
        self.interactor?.feedback(for: self.put up)
        .onSuccess(queue: .fundamental) { feedback in
            self.view?.show(feedback)
        }
        .onFailure(queue: .fundamental) { error in
            
        }
    }

    func toggleBookmark() {
        self.router?.toggleBookmark(for: self.put up)
        self.view?.setup(with: self.interactor!.bookmark(for: self.put up))
    }
}

Within the builder methodology I can entry each element of the VIPER module so I can merely cross across the variable to the designated place (identical applies for the delegate parameter). I often set enter variables on the presenter and delegates on the router.

It is often a presenter who wants information from the unique module, and I wish to retailer the delegate on the router, as a result of if the navigation sample adjustments I haven’t got to vary the presenter in any respect. That is only a private choice, however I like the way in which it seems like in code. It is actually laborious to put in writing down these items in a single article, so I would advocate to obtain my completed pattern code from github.


Abstract

As you possibly can see I am utilizing varied design patterns on this VIPER structure tutorial. Some say that there is no such thing as a silver bullet, however I consider that I’ve discovered a very superb methodology that I can activate my benefit to construct high quality apps in a short while.

Combining Guarantees, MVVM with assortment views on prime of a VIPER construction merely places each single piece into the proper place. Overengineered? Possibly. For me it is definitely worth the overhead. What do you consider it? Be happy to message me via twitter. You can too subscribe to my month-to-month publication beneath.



[ad_2]

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments