Better Programming

Advice for programmers.

Follow publication

Welcome to the MVVM-Router With SwiftUI and Combine

A ViewModel full of promise

Martin Lukacs
Better Programming
Published in
5 min readOct 17, 2020

--

Two rockets launching
Photo by SpaceX on Unsplash

Hello, everyone. Like many of you, I have recently started my journey in the new and strange world of SwiftUI and Combine, and I must say that I'm quite enjoying the ride for the most part. Yes of course it's new, it's flashy, and it's young, but it's also full of promises (Combine, wink wink). I'm really hoping that these tools mature well and get enough momentum to stick around for us iOS devs.

But let's not get distracted and instead focus on the goal of this article. As with any new tools, it takes some trial and error to figure out the best way of using them and getting the best out of them. With great power comes great… (whispers in my ear are telling me I'm on the right track).

In this article, you'll find my take on an MVVM implementation of SwiftUI and Combine in a very simple project.

All the tools and modules you'll find in the project are just there to showcase how we could implement the famous MVVM pattern while trying to avoid some of the pitfalls brought about by the new iOS language coding tools.

But less talking and more code display.

The Sample Project

As mentioned earlier, the project created for this article is very simplified.

The tools and feature implementation are far from being production-ready and are just there to serve as an example and base for reflection for further improvement.

The source code can be found here: SpaceXSwiftUICombine.

The main goal is to display all SpaceX launches to the user while adding a few small data-processing functionalities (search and filter).

Here's what you can find by digging in the source code:

  • Combine Networking
  • Single source of truth for data (combination of Combine tools/dependency injection)
  • Centralised routing coordinator
  • Lots of dependency injection
  • Lots of protocols: 0
  • Some search and filter functionality

But overall the core structure should look very similar to previous MVVM implementations you may already be familiar with.

The Main Feature of This MVVM-Router

Centralised routing coordinator

If you've already worked a bit with SwiftUI, you must have noticed very early on that views are very tightly coupled to each other. NavigationLinks or sheets usually call the next view in the navigation flow by name. This makes any future changes in navigation very complicated to tackle (especially in a complex app navigation environment). My approach was to try to centralise the routing in a single place that can be shared or passed on to any viewModel.

As you can see in the above code, the mainRouter is based on protocols that help to decouple the views. This can be done by setting a router in the view model and calling it when needed in the view as demonstrated in the next two examples.

And voila, your view is oblivious to what "routeToLaunches()" or "routeToRockets()" represents.

This also works with routing that requires data sharing as you can see with the route to "DetailLaunchView."

Single source of truth for data

This feature is easily implemented with the combination of dependency injection, use of a specific repository for data management, and Combine.

It's not new but it works well.

As with the routing protocols, we leverage the power of protocols to lessen coupling. The repository can be passed by dependency injection to any part of the app. Just subscribe to the publishers declared in the protocol and you have access to the latest and freshest stream of data.

Examples are worth a thousand words:

  1. Declare your data sharing repository
  2. Inject the component
  3. Subscribe & assign or use sent data. Works really well in combination to @Published variable.
  4. You can then apply wanted transformations to data
  5. Apply those changes on local data at your will.

Overall, it's a breeze to implement these types of techniques with SwiftUI and Combine and some kind of DI. If you feel lazy, there are a plethora of Dependency injection libraries that should help (Resolve, SwiftInject, and so on).

The ease of data processing

As a last pointer, I wanted to quickly go over the nice symbiosis that already exists between SwiftUI and Combine.

For that, you are just going to focus on the implementation of a very simple search and filter bar.

Above is a simple grid view displaying space X launches.

  1. Binding between view and viewModel. In this case, the TextField content, and the viewModel query variable.
  2. Binding between the view and the viewModel filter.
  3. Linking the Gridview displayed data to the viewModel launches.

Next, let's see how to process the incoming data in the viewModel.

The magic happens in the filteredLaunchesPublisher variable. Combine, debounce, process, and assign. In very few lines of code, we have a fully functional search and filter on our data.

  1. @Published variables helping with the binding of data between viewModel and the view.
  2. Creation of a publisher for processing the data. In this case, it’s the combination of our mains stream of truth: launches, query, and applied filter.
  3. Assign the processing publisher to local bounded variable to display up to date data.

A Few Tips and Tricks to Try

I'll conclude this article with a few tips and tricks I have picked up while working with SwiftUI and Combine.

Assign strong ref

First, it seems that combine assign creates a strong reference between elements. To combat this problem, someone in forums.swift.org has come up with a nice little piece of code. I would suggest using that.

Lazy loading NavigationLink subviews

Another strange finding is that the view set in NavigationLink destination is not always lazy-loaded. In fact, it seems that it is quite the opposite. This could end up making your app load massive amounts of data at launch. Once again StackOverflow is our friend. And one of our colleagues has come up with a nice little piece of code to circumvent the problem. You can find more information here. The trick is:

Then, you use it like so:

NavigationLink(destination: LazyView( ** Put your destination view **)) { ** NavigationLink UI ** }

I hope this little overview of how MVVM can be implemented with SwiftUI and Combine has helped you. I hope it has given you some good ideas and has cleared up any questions you had.

Please feel free to reach out if you would like more information on specific topics. I'm also always looking to discuss how to improve, modify, or add on to previously discussed topics.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Martin Lukacs
Martin Lukacs

Written by Martin Lukacs

A Swift enthusiast / iOS Software Engineer at Deezer

No responses yet

Write a response