Chapters

Hide chapters

Advanced iOS App Architecture

Fourth Edition · iOS 15 · Swift 5.5 · Xcode 13.2

Before You Begin

Section 0: 4 chapters
Show chapters Hide chapters

Section I

Section 1: 9 chapters
Show chapters Hide chapters

5. Architecture: MVVM
Written by René Cacheaux & Josh Berlin

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Model-View-ViewModel (MVVM) is the new trend in the iOS community, but its roots date back to the early 2000s at Microsoft. Yes, you read that correctly! Microsoft. Microsoft architects introduced MVVM to simplify design and development using Extensible Application Markup Language (XAML) platforms, such as Silverlight.

Prior to MVVM, designers would drag and drop user interface components to create views, and developers would write code for each view specifically. This resulted in the tight coupling between views and business logic — changing one typically required changing the other. Designers lost freedom due to this workflow: They became hesitant to change view layouts because, doing so, often required massive code rewrites.

Microsoft specifically introduced MVVM to decouple views and business logic. This alleviated pain points for designers: They could now change the user interface, and developers wouldn’t have to change too much code.

Fast forward to iOS today, and you’ll find that iOS designers usually don’t modify Xcode storyboards or auto layout constraints directly. Rather, they create designs using graphical editors such as Adobe Photoshop. They hand these designs to developers, who, in turn, create both the views and code. Thereby, the goals of MVVM are different for iOS.

MVVM isn’t intended to allow designers to create views via Xcode directly. Rather, iOS developers use MVVM to decouple views from models. But the benefits are the same: iOS designers can freely change the user interface, and iOS developers won’t need to change much business logic code.

What is it?

MVVM is a “reactive” architecture. The view reacts to changes on the view model, and the view model updates its state based on data from the model.

MVVM involves three layers:

  • The model layer contains data access objects and validation logic. It knows how to read and write data, and it notifies the view model when data changes.

  • The view model layer contains the state of the view and has methods to handle user interaction. It calls methods on the model layer to read and write data, and it notifies the view when the model’s data changes.

  • The view layer styles and displays on-screen elements. It doesn’t contain business or validation logic. Instead, it binds its visual elements to properties on the view model. It also receives user inputs and interaction, and it calls methods on the view model in response.

As a result, the view layer and model layer are completely decoupled. The view layer and model layer only communicate with the view model layer.

Next, you’ll go into the each of these layers in depth.

Model layer

The model layer is responsible for all create, read, update and delete (CRUD) operations.

Repository pattern

Repositories contain data access objects that can call out to a server or read from disk.

Repository structure

The repository provides a set of asynchronous CRUD methods. The underlying implementations can be either stateless or stateful. Stateless implementations don’t keep data around after retrieving it, whereas stateful implementations save data for later. The components are usually stateful and keep data in-memory for quick access.

Example: KooberUserSessionRepository

In Koober, signing up or signing in creates a new session. The session contains the current user’s authentication token and metadata, such as name and avatar.

View layer

A view is a user interface for a screen. In MVVM, the view layer reacts to state changes through bindings to view model properties. It also notifies the view model of user interaction, like button taps or text input updates.

View model layer

The view model is the life of the party in this chapter. It contains a view’s state, methods for handling user interaction and bindings to different user interface elements.

Example: Koober sign-in view model

The sign-in view model contains business logic for signing in to Koober and publishers to update state.

Creating the view

The view knows how to style and layout its subviews, as well as hook up user interface elements to the view model publishers. In Koober, view controllers create the view and the view model inside loadView(). The view controller creates the view model first and passes it to the view. Since Koober creates view layouts in code, views can have a custom initializer.

Container views

Each screen in Koober has a container view — a top-level view that contains other child views. The container view’s purpose is to build a complex screen out of modular views. Instead of throwing all the user interface into one massive view, keep your views small, focused and reusable.

Structuring container views

A dependency container initializes a container view with its child views. A container view adds and displays child views in its view hierarchy. Child views limit the responsibility of the top-level container view. The number of child views needed depends on the screen’s complexity. Each child view is reusable and performs all its work independently.

Example: Koober ride request

Communicating amongst view models

Sometimes, view models need to signal out to the rest of the app when state changes. If a task is outside of the responsibility of one view model, the application may need to notify another view model to take over.

Collaborating view models

Normally state changes in a view model update a view. Sometimes, those state changes affect the entire app. View models don’t know how to post app-wide notifications; they take inputs and produce outputs. One way for view models to communicate with the app is to call into another view model, forming a graph of view models.

Navigating

Model-driven navigation

In model-driven navigation, view models contain a view enum describing all possible navigation states. The system observes this and navigates to the next screen when the value changes.

System-driven navigation

System-driven navigation is any navigation managed by the system. For example, gestures that trigger scroll view page navigation, or tapping a Back button in a navigation stack, automatically navigate the user to the previous screen.

Combination

You can use built-in, system-driven navigation to your advantage, while still implementing an MVVM architecture. For example, you can use model-driven navigation to move a navigation stack forwards and use system-driven navigation to move backwards.

Managing state

Some navigation schemes create new views when navigating, and other schemes hold onto views and reuse them.

Creating new views on navigation

Creating a new view each time a view is presented is easier to manage. The view and view model aren’t held in memory when the view is offscreen.

Reusing views on navigation

Reusing views makes sense when they need to preserve their state. System containers, like tab bars and navigation controllers, reuse views on navigation.

Applying theory to iOS apps

Congratulations for making it to the code examples - the kangaroos are proud! You’ve just learned a ton of theory about MVVM.

Building a view

The sign-in screen allows you to authenticate with Koober. The initial state shows placeholders for empty email and password fields. The Sign In button is always active, even when the text fields are empty. Tapping the button validates the email and password, and shows an error if either field is empty or if the API call returns an error.

Model layer

The sign-in model layer does most of the authentication work. It authenticates with the Koober server and persists the user session. The repositories are in KooberKit/DataLayer/Repositories and the models are in KooberKit/DataLayer/Model.

View model layer

SignInViewModel is where all the reactive magic happens in the sign-in screen. It holds all the view’s state, and it signs the user in.

View layer

We created all Koober root views in code instead of using storyboards. The kangaroos made us do it! No, really, there’s a valid reason for this. Root views get a view model injected on initialization. Using storyboards, this would be impossible. Also, in-code constraint creation is a lot easier these days. But that’s a debate for another day.

Composing views

The pick-me-up screen is the heart of the Koober app. This is where the ’roos hop around and fulfill their ride-sharing destinies.

Pick-me-up container view

Ride-option picker view controller

Ride-option picker segmented control

RideOptionSegmentedControl displays a button for each Koober ride option:

Ride-option picker view model

Next, you’ll go into the view model in more depth.

Pick-me-up view model

PickMeUpViewModel describes the state of the pick-me-up screen using enums:

Navigating

This section is all about navigation. You’ll learn different techniques for driving navigation, how to manage initial view state on navigation and managing scopes when transitioning from onboarding to signed in.

Driving navigation

Koober uses three main techniques for driving navigation:

Model-driven navigation

The transitions from the map to the drop-off selection screen and drop-off selection screen back to the map use model-driven navigation.

System-driven navigation

Koober doesn’t use pure system-driven navigation anywhere in the app. So leave Koober land for a bit and take a look at a simple UITabBarController example.

Combination

The onboarding screen uses model-driven navigation from the welcome screen to the sign-in screen, and it uses system-driven navigation backwards to the welcome screen.

Managing state

When you navigate between screens, there are two ways to manage state:

New views on navigation

Creating a new view each time you present a new screen makes state management easier. You guarantee the screen starts from the initial state each time it’s presented.

Reusing views on navigation

Reusing views on navigation makes state management harder. Each time you present a new screen, you need to make sure the state is reset back to the original state. The onboarding flow is an example of reusing views on navigation.

Managing scopes: Onboarding to signed in

During the onboarding flow, no authenticated user exists. There’s no reason to create a map, since the map needs an authenticated user to work.

Pros and cons of MVVM

Pros of MVVM

  1. View model logic is easy to test independently from the user interface code. View models contain zero UI — only business and validation logic.
  2. View and model are completely decoupled from each other. View model talks to the view and model separately.
  3. MVVM helps parallelize developer workflow. One team member can build a view while another team member builds the view model and model. Parallelizing tasks gives your team’s productivity a nice boost.
  4. While not inherently modular, MVVM does not get in the way of designing a modular structure. You can build out modular UI components using container view and child views, as long as your view models know how to communicate with each other.
  5. View models can be used across Apple platforms (iOS, tvOS, macOS, etc.) because they don’t import UIKit. Especially if view models are granular.

Cons of MVVM

  1. There is a learning curve with Combine (compared to MVC.) New team members need to learn Combine and how to properly use view models. Development time may slow down at first, until new team members get up to speed.
  2. Typical implementation requires view models to collaborate. Managing memory and syncing state across your app is more difficult when using collaborating view models.
  3. Business logic is not reusable from different views, since business logic is inside view specific view models.
  4. It can be hard to trace and debug, because UI updates happen through binding instead of method calls.
  5. View models have properties for both UI state and dependencies. This means that view models can be difficult to read, because state management is mixed with side effects and dependencies.

Key points

Where to go from here?

Koober is meant to be a real-world use case, and there’s a ton of code in the example project we couldn’t cover in one chapter. Feel free to explore the codebase on your own.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now