MVVM with SwiftUI and Combine

An Tran • June 11, 2019

ios swiftui

Before WWDC 2019

Apple has always advertised MVC as the preferred architecture to develop iOS apps. So the community comes up with different alternative architectures to solve the "big view controllers" problem. MVVM is one of the most popular architecture used by iOS developers. Before WWDC 2019, most of MVVM implementations use some popular reactive framework such as RxSwift, ReactiveSwift or ReactiveKit along with their UI binding counterparts such as RxCocoa, ReactiveCocoa or Bond.

After WWDC 2019

In WWDC 2019, Apple has introduced two awesome frameworks which replace the above frameworks to provide reactive programming and UI bindings. From now on, developers can implement MVVM architecture for their apps using only native frameworks which provide many benefit such as stability, binary app size and consistent APIs etc...

Apple seems also to go away from MVC and adopt MVVM as the default architecture for apps development.

MVVM

With SwiftUI, you don't have to decide if you want to use visual tools or writting code to design, you can have both at the same time.

Some WWDC videos related to this topic:

MVVM using Swift and Combine

I've created a simple project to demonstrate how we can use those native frameworks to implement MVVM architecture for iOS apps. The code is here on github: https://github.com/peacemoon/PixabaySwiftUICombine/

This project demonstrates how to implement MVVM using SwiftUI and Combine to search and display images from pixabay.com. There are also some tests to demonstrate the testability of the implementation.

The View is written using SwiftUI framework. Each View has its own ViewModel which is responsible to call a Service to fetch data DataModel from the remote server:

// View Model
func search(withTerm term: String) {

    self.isActive = true

    let dataUpdater = AnySubscriber<ImageListModel, Error>(
        receiveValue: { [weak self] imageListModel -> Subscribers.Demand in
            self?.images = imageListModel.hits
            return .unlimited
        },
        receiveCompletion: { [weak self] completion in
            if case let .failure(error) = completion {
                print(error)
                self?.images = []
            }
            self?.isActive = false
        }
    )

    pixaBayService.fetch(searchTerm: term).subscribe(dataUpdater)
}

In View, ViewModel is declared as ObjectBinding to provide binding functionality, so that the View gets informed when ViewModel gets new data from the Service:

@ObjectBinding var viewModel = ImageListViewModel(pixaBayService: PixaBayService())

The Service uses Combine, specifically Future to return data from an asynchronous call:

func fetch(searchTerm: String) -> Publishers.Future<ImageListModel, Error> {
    let urlString = "https://pixabay.com/api/?key=107764-f19c20d5ca4d545d9b0a09de3&q=\(searchTerm)&image_type=photo&pretty=true"
    let url = URL(string: urlString)!

    return Publishers.Future { resolver in

        URLSession.shared.dataTask(with: url) { data, _, error in
            guard error == nil else {
                return resolver(Result.failure(error!))
            }

            guard let data = data else {
                return resolver(Result.failure(ServiceNetworkError.noData))
            }

            do {
                let decoder = JSONDecoder()
                let imageData = try decoder.decode(ImageListModel.self, from: data)
                return resolver(Result.success(imageData))
            } catch {
                return resolver(Result.failure(error))
            }
        }.resume()
    }
}

Conclusion

SwiftUI and Combine bring great foundations to build iOS apps with MVVM architecture. The best benifit is that you don't have to rely on 3rd-party frameworks for your implementations. Native frameworks are always the best choices for awesome apps.

Github repo: https://github.com/peacemoon/PixabaySwiftUICombine/