Swift Tip: Separating UI and Model Code
Our current Swift Talk series looks at different techniques to refactor a large view controller, using an example from the open source Wikipedia iOS app.
In the first three episodes, we extract pure functions, model code, and networking-related code. You may notice a common theme to all these refactorings: in each episode we try to separate the code which updates the UI from the code that provides the data for updating the UI.
For example, in last week's episode we start with a method like this:
class PlacesViewController: UIViewController {
func updateSearchCompletionsFromSearchBarTextForTopArticles() {
// ...
}
}
This method is called whenever the text in the search bar changes. It performs these main tasks:
-
Request search completions from Wikipedia for the current search term
-
Transform the results to the type needed to drive the UI
-
Request location-based completions if the first request doesn't return enough results
-
Transform the second batch of results
-
Update the UI with the completions after each request
-
Control the state of a progress bar at various success and failure points
The first thing we do is to separate this method into two; one method deals with the logic for search completion fetching, and the other only updates the UI:
func updateSearchCompletionsFromSearchBarTextForTopArticles() {
fetchSearchSuggestions(for: searchBar?.text ?? "") { result, done in
// Update UI
}
}
func fetchSearchSuggestions(for searchTerm: String, completion: @escaping ([PlaceSearch], Bool) -> ()) {
// ...
}
To prevent breaking any other code in the view controller, we keep the existing method signature. However, the original method now only deals with updating the UI in response to search suggestions; all the fetching logic is handled by the second method, which uses a completion handler to asynchronously report its results.
With this separation in place, we can easily move the non-UI method out of the view controller — for example into a model or a service class.
You can watch the full refactoring in Swift Talk #105.
We often find this approach very helpful for writing maintainable view controllers. When we refactor, we always try to pull out code from a view controller that fetches, transforms, or prepares data. The view controller should be left with the task of updating the views.