Getting Started With the VIP Clean Architecture Pattern

In this tutorial, you’ll learn how to utilize the VIP clean architecture pattern to develop apps for Apple platforms while building a SwiftUI for ordering an ice cream. By Danijela Vrzan.

4.3 (12) · 9 Reviews

Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Creating a Model

In Xcode, create a Swift File named CreateIceCreamModel.swift in the CreateIceCream group.

Add the following code to the file:

enum CreateIceCream {
  enum LoadIceCream {
    struct Request {}

    struct Response {
      var iceCreamData: IceCream
    }

    struct ViewModel {
      var cones: [String]
      var flavors: [String]
      var toppings: [String]
    }
  }
}

Here, you create Request, Response and ViewModel data models.

In this case, your request is empty. The view knows it needs to request something but doesn’t know the type of data. When the interactor receives the request, it loads the data and sends IceCream as a response. When the presenter receives the response, it formats the data for the view model and sends it to the view as an Array of strings.

LoadIceCream wraps the data model and represents a single functionality of your code, loading the ice cream and showing it in the view.

If the app had extra logic such as saving or deleting the data, you’d create separate enums called SaveIceCream and DeleteIceCream. Both of them would then have their own data models.

The advantage of nesting your data models like this is readability. For instance, if you wanted to create a request for LoadIceCream, you’d do it like this:

let request = CreateIceCream.LoadIceCream.Request()

It’s easy to understand which scene and functionality this request belongs to.

Next, you’ll set up the view.

Setting Up the View

The view is already created for you and contains your app’s UI. You’re going to add the display logic and call the interactor to load the data.

First, open CreateIceCreamView.swift. At the top of the file is an extension with a helper method for displaying the image. You’ll implement the display logic methods in the extension.

But before you do that, your view needs to know about the interactor.

At the top of CreateIceCreamView, replace // TODO: Add interactor with:

var interactor: CreateIceCreamBusinessLogic?

This links the interactor with the view through the CreateIceCreamBusinessLogic protocol.

Components communicate with each other through protocols. This makes each component of a scene less tightly tied to other components. The view asks the interactor to perform the business logic but doesn’t know how it’s done.

The compiler will complain it cannot find the type in scope. Don’t worry; you’ll fix this in the following section, where you’ll create the protocol in the interactor. Having the protocol in the same file with the component is a typical feature of using the VIP pattern.

Next, replace // TODO: Call interactor to fetch data with:

func fetchIceCream() {
  let request = CreateIceCream.LoadIceCream.Request()
  interactor?.loadIceCream(request: request)
}

fetchIceCream() creates a request and calls the interactor’s loadIceCream(request:), passing the request inside.

It might seem redundant to create and pass an empty request. You’ll create it in this example to understand how passing the data works between components. But it’s a good idea to have it in case your logic changes.

Finally, scroll down to the end of the file and, after navigationTitle("Scoops&Scones"), add:

.onAppear {
    fetchIceCream()
}

Now, when your app’s view appears, it’ll call the added fetchIceCream().

Next, you’ll set up the interactor.

Setting Up the Interactor

Create a Swift File named CreateIceCreamInteractor.swift in the CreateIceCream group.

Add the following to the file:

protocol CreateIceCreamBusinessLogic {
  func loadIceCream(request: CreateIceCream.LoadIceCream.Request)
}

class CreateIceCreamInteractor {
  var presenter: CreateIceCreamPresentationLogic?
}

You’ve fixed the previous compiler error, but now it’s complaining again. You’ll create a presenter in the following section that fixes the error.

In the code above, you create a new CreateIceCreamInteractor class to handle the scene’s business logic. Then, you define the presenter so the interactor can pass the response to it.

As already mentioned, protocols allow you to decouple your components. The interactor passes the response to the presenter but doesn’t know who’s presenting the data or how.

Next, add the following extension to the bottom of the file:

extension CreateIceCreamInteractor: CreateIceCreamBusinessLogic {
  func loadIceCream(request: CreateIceCream.LoadIceCream.Request) {
    // TODO
  }
}

CreateIceCreamInteractor conforms to CreateIceCreamBusinessLogic protocol and implements loadIceCream(request:).

Next, replace // TODO with:

let iceCream = Bundle.main.decode(IceCream.self, from: "icecream.json")
let response = CreateIceCream.LoadIceCream.Response(iceCreamData: iceCream)
presenter?.presentIceCream(response: response)

In the code above, you decode the json file into IceCream domain model and store it inside iceCream.

NOTE: Bundle.decode(_:from:) is an extension of Bundle. You can find it in the project’s Extensions group.

You create a new response with the decoded IceCream data. Then, you call the presenter’s presentIceCream(response:) and pass the response to the presenter.

The interactor is your app’s brain and handles all the business logic such as loading, deleting or saving the data. But there’s another component you could add to the interactor, called Worker.

Look at this diagram:

Diagram showing the VIP pattern cycle and how workers communicate with the interactor bidirectionally

You can have multiple workers for the interactor, with each handling a specific logic. If your app fetched the data from an API, you’d create a NetworkWorker and have all the networking logic inside. If your app saved the data using CoreData, you’d add a CoreDataWorker and so on.

Now, you need to create and set up the presenter, the final component.

Setting Up the Presenter

Create a Swift File named CreateIceCreamPresenter.swift in the CreateIceCream group.

Add the following code to the file:

protocol CreateIceCreamPresentationLogic {
  func presentIceCream(response: CreateIceCream.LoadIceCream.Response)
}

class CreateIceCreamPresenter {
  var view: CreateIceCreamDisplayLogic?
}

The compiler will complain once more about the unknown type in scope. You’ll finally fix all the errors in the following section, where you’ll create the CreateIceCreamDisplayLogic protocol inside the view.

In the code above, you create a new CreateIceCreamPresenter class to handle the scene’s presentation logic. Then, you define the view so the presenter can format the data from the interactor and pass it to the view.

Next, add the following extension at the bottom of the file:

extension CreateIceCreamPresenter: CreateIceCreamPresentationLogic {
  func presentIceCream(response: CreateIceCream.LoadIceCream.Response) {
    // TODO
  }
}

The presenter conforms to CreateIceCreamPresentationLogic protocol and implements presentIceCream(response:).

Next, replace // TODO with:

let viewModel = CreateIceCream.LoadIceCream.ViewModel(
  cones: response.iceCreamData.cones,
  flavors: response.iceCreamData.flavors,
  toppings: response.iceCreamData.toppings
)
view?.displayIceCream(viewModel: viewModel)

The presenter formats the response into three arrays of strings and passes it to the ViewModel. The view model is then passed to displayIceCream(viewModel:) on the view to display the formatted data.

Now, you need to add the display logic protocol to your view.