MVVM (model-view-viewModel) is an architecture pattern that is an alternative to MVC (model-view-controller). MVVM, sometimes referred to as presentation model, offers a way to organize code that doesn’t result in massive view controllers.
In MVVM, you abstract your code to include a viewModel, which is a file that holds the values to be presented in your view. The logic we write to format the values (i.e. formatting a string to be inserted into a UILabel) to be presented to the view takes place in the viewModel.
The View Controller owns the viewModel which owns the Model and in turn, the Model updates the View Model, which is in turn, used to update the View Controller, which updates the view.
In this blog, I will show you a simple example of how to abstract code using the MVVM architecture pattern. To illustrate, I will make an app that will display the current top 100 free apps in the iTunes store. The app will use a tableView and require networking to the iTunes API endpoint. I’ll include my networking steps in my process.
Step 1: Set up the APIClient
The API I used was from iTunes’ RSS Feed Generator and does not require a key or authorization token. The endpoint is: https://rss.itunes.apple.com/api/v1/us/ios-apps/top-free/100/explicit/json
#1: I create an APIClient class that inherits from NSObject. Inheriting from NSObject is important when I instantiate this APIClient in my viewModel later.
#2: I write a fetchAppList function that has a completion handler. The completion handler will include an optional array of NSDictionaries. The JSON data will come back as an array of dictionaries for the key “results” below.
#3: I create and unwrap a URL from my iTunes endpoint.
#4: Create an URLSession.
#5: From our dataTask, unwrap our returned data.
#6: Since JSON serialization throws an error, use do/try/catch to create our JSON object and handle our error. I unwrap my responseJSON object and cast it as an NSDictionary and choose the .allowFragments option.
#7: From my responseJSON object, I have an array of dictionaries for my “results” key so I access this and create an object and cast it as an array of NSDictionaries. Note how I access this from a keyPath “feed.results”. You can access JSON dictionary values by keyPath! 🌮
#8: I set my completion handler with my apps array of NSDictionaries.
#9: If I received an error, I set my completion handler with nil.
#10: I resume my dataTask. Always remember to do this.
Step 2: Setup the ViewModel.
#1: I create a ViewModel file that inherits from NSObject. This viewModel will be a property in my View Controller and injected by the storyboard.
#2: I create an apiClient property marked as an @IBOutlet that will be instantiated by the storyboard. Since I know the storyboard will be injecting it, I can use the (!) bang operator because I know it will not be nil.
#3: I define an apps dictionary that will hold the apps retrieved by my apiClient call to the iTunes API.
#4: Write a function that will trigger the apiClient call to the iTunes API, return data and set up a completion handler that will be ready to reload our data to be displayed.
#5: Call on our apiClient property to fetch our apps.
#6: Put this block of code on the main thread because we anticipate how it will affect the UI in our table view in our View Controller.
#7: Assign our local apps array to our returned JSON of NSDictionaries.
#8: Call our completion handler so that we will know when we can start reloading the data in our View Controller.
Next, we write functions that we can call in the table view controller. These functions will provide the values to be displayed in the table view controller’s cells.
#9: Write a function that takes in a tableView section index (for the sake of this example though, we are only going to have one section) and returns the number of cell rows for that section. Here, I return the count of the size of the apps array.
#10: Write a function that returns the name of an app. Note here that since our apps array contains the NSDictionaries with info for each app, I find the value in each app NSDictionary for the keyPath “name”.
#11: Write a function that returns the rating of an app. Note here that since our apps array contains the NSDictionaries with info for each app, I find the value in each app NSDictionary for the keyPath “contentAdvisoryRating”.
Note how I return either empty strings or “0” if I am unable to cast the values a string or integer.
Step 3: Setup the Table View Controller
#1: Declare an optional viewModel property with an @IBOutlet. The storyboard will instantiate this viewModel object.
#2: In viewDidLoad( ), access our viewModel property to get our app object.
#3: Reload the tableView to display our retrieved app objects.
Assign the our app values from our viewModel in our tableView methods.
#4: Call on the viewModel’s numberOfItemsToDisplay(_:) function to return the number of rows.
#5: Populate the cells with values from the viewModel.
#6. Create a cell object with the cell identifier “cell”.
#7 and #8: Call on the viewModel’s appTitleToDisplay(_:) function and appRatingToDisplay(_:) function to return the app name (as a string) and app rating (as a string) to display in the tableView.
Step 4: Setup Storyboard
#1: Replace the View Controller with a TableViewController.
#2: Set the class to the TopAppsTableViewController.swift file.
#3: Select the cell and change its style to Right Detail.
#4: Set the identifier to “cell” (which will match the identifier we wrote in the TopAppsTableViewController.swift file).
#5: From the object library, drag an object onto your table view controller and insert it at the top.
#6: Select the object at the top of the table view controller.
#7: Set this object’s class to the ViewModel.swift file you created.
#8 and #9: Use assistant editor to control-drag from the ViewModel object to the viewModel @IBOutlet you created in the TopAppsTableViewController.swift file.
#10: This connection should now show up in your Connections Inspector.
#11: Drag another object from the Object Library onto the table view controller.
#12: Select this new object and assign it to your APIClient.swift file.
#13: Control-drag from the viewModel object to the Api Client object you just created. Make sure that this outlet shows up in the Connections Inspector.
#14: Make sure this table view controller is set as the initial view controller in the Attributes Inspector.
Build and run. You’ve just used MVVM to separate your model value logic from your view logic by using a viewModel class to house the values to populate your tableview.
See the final product:
The repo for this blog project can be found here.