Building a SwiftUI app using MVVM architecture — Part 5: Connecting the models with the UI using MVVM

Jullian Mercier
2 min readSep 22, 2019

--

The MVVM architecture is a perfect fit for SwiftUI as one can make a view model conform the ObservableObject and mark its underlying properties as @Published so that views always reflect the latest model changes.

Focusing on these four @Published properties from the Headlines app.

@Published var preferences: UserPreferences
@Published var headlines: [Headlines] = []
@Published var isLoading = true
@Published var keyword: String = “”

The @Published attribute is a property wrapper which allows access to a Publisher type using either the `$` syntax or the projectedValue struct property.

@propertyWrapper public struct Published<Value> {    /// Initialize the storage of the Published property as well as the corresponding `Publisher`.
public init(initialValue: Value)
{…} /// The property that can be accessed with the `$` syntax and allows access to the `Publisher`
public var projectedValue: Published<Value>.Publisher { mutating get }
}

New values assigned to the @Published `keyword` property can be then processed using the underlying Publisher type.

self.$keyword  // or self._keyword.projectedValue 
.debounce(for: 0.5, scheduler: DispatchQueue.main)
.sink { self.sortArticles(byKeyword: $0) }
.store(in: &cancellable)

The `sink` method creates a subscriber and immediately receive values from the upstream publisher enabling us to sort the articles by keyword.

Notice how the `$` syntax or `projectedValue` property from @Published is similar to the @State ones however both provide with different types —respectively a Publisher type and a Binding type — .

Our views, while observing these @Published properties from our ObservableObject, will actually react to any new values emitted by these underlying publishers through the `objectWillChange` synthesizer.

This shows how tightly related SwiftUI and Combine are.

When the user select new preferences, the `UserPreferences` object is mutated accordingly and changes are automatically reflected to the UI using the @ObservedObject property from the views.

struct PreferencesView: View {
@ObservedObject var viewModel: HeadlinesViewModel
{...}
}

Data is retrieved from the web-service and assigned to the headlines property triggering a new UI update. In the meantime, `isLoading` is set to false which hides the loader and presents the latest data on screen.

func fire() {    let data = webService.fetch(preferences: preferences).map { value -> [Headlines] in        let headlines = value.map { (section, result) -> Headlines in
let isFavorite = self.preferences.categories
.first(where: { $0.name == section })?
.isFavorite
return Headlines(
name: section, isFavorite: isFavorite ?? false,
articles: result.articles
)
}
return headlines.sortedFavorite() }.handleEvents(receiveSubscription: { _ in
self.isLoading = true
}, receiveOutput: { _ in
self.isLoading = false
})
.receive(on: DispatchQueue.main)
.assign(to: \HeadlinesViewModel.headlines, on: self)
cancellable?.insert(data)
}

— Flow

Views use an @ObservedObject property to observe the view model through its @Published properties. Whenever these properties are mutated, the UI is updated.

Continue with Part 6: Integrating UIKit.

--

--

Jullian Mercier
Jullian Mercier

Written by Jullian Mercier

Senior iOS engineer. jullianmercier.com. @jullian_mercier. Currently looking for new job opportunities.

No responses yet