https://github.com/thoughtbot/combineviewmodel
An implementation of the Model-View-ViewModel (MVVM) pattern using Combine.
https://github.com/thoughtbot/combineviewmodel
Last synced: 3 months ago
JSON representation
An implementation of the Model-View-ViewModel (MVVM) pattern using Combine.
- Host: GitHub
- URL: https://github.com/thoughtbot/combineviewmodel
- Owner: thoughtbot
- License: mit
- Created: 2020-08-03T18:56:44.000Z (almost 6 years ago)
- Default Branch: main
- Last Pushed: 2020-12-08T16:15:12.000Z (over 5 years ago)
- Last Synced: 2025-04-15T13:03:57.030Z (about 1 year ago)
- Language: Swift
- Size: 1.9 MB
- Stars: 60
- Watchers: 2
- Forks: 4
- Open Issues: 8
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# CombineViewModel
An implementation of the Model-View-ViewModel (MVVM) pattern using Combine.
- [Introduction](#introduction)
- [Installation](#installation)
- [Bindings](#bindings)
- [Contributing](#contributing)
- [About](#about)
## Introduction
CombineViewModel’s primary goal is to make view updates as easy in UIKit and
AppKit as they are in SwiftUI.
In SwiftUI, you write model and view-model classes that conform to Combine’s
[`ObservableObject`][ObservableObject] protocol. SwiftUI:
1. Observes each model’s `objectWillChange` publisher via the
[`@ObservedObject`][ObservedObject] property wrapper, and;
2. Automatically rerenders the appropriate portion of the view hierarchy.
The problem with `objectWillChange` _outside_ of SwiftUI is that there's no
built-in way of achieving (2) — being notified that an object _will_ change is
not the same as knowing that it _did_ change and it’s time to update the view.
[ObservableObject]: https://developer.apple.com/documentation/combine/observableobject
[ObservedObject]: https://developer.apple.com/documentation/swiftui/observedobject
### `ObjectDidChangePublisher`
Consider the following sketch of a view model for displaying a user’s social
networking profile:
```swift
// ProfileViewModel.swift
import CombineViewModel
import UIKit
class ProfileViewModel: ObservableObject {
@Published var profileImage: UIImage?
@Published var topPosts: [Post]
func refresh() {
// Request updated profile info from the server.
}
}
```
With CombineViewModel, you can subscribe to _did change_ notifications using
the `observe(on:)` operator:
```swift
let profile = ProfileViewModel()
profileSubscription = profile.observe(on: DispatchQueue.main).sink { profile in
// Called on the main queue when either (or both) of `profileImage`
// or `topPosts` have changed.
}
profile.refresh()
```
### Automatic view updates
Building on `ObjectDidChangePublisher` is the `ViewModelObserver` protocol and
`@ViewModel` property wrapper. Instead of manually managing the
`ObjectDidChangePublisher` subscription like above, we can have it managed
automatically:
```swift
// ProfileViewController.swift
import CombineViewModel
import UIKit
// 1️⃣ Conform your view controller to the ViewModelObserver protocol.
class ProfileViewController: UITableViewController, ViewModelObserver {
enum Section: Int {
case topPosts
}
@IBOutlet private var profileImageView: UIImageView!
private var dataSource: UITableViewDiffableDataSource!
// 2️⃣ Declare your view model using the `@ViewModel` property wrapper.
@ViewModel private var profile: ProfileViewModel
// 3️⃣ Initialize your view model in init().
required init?(profile: ProfileViewModel, coder: NSCoder) {
super.init(coder: coder)
self.profile = profile
}
// 4️⃣ The `updateView()` method is automatically called on the main queue
// when the view model changes. It is always called after `viewDidLoad()`.
func updateView() {
profileImageView.image = profile.profileImage
var snapshot = NSDiffableDataSourceSnapshot()
snapshot.appendSections([.topPosts])
snapshot.appendItems(profile.topPosts)
dataSource.apply(snapshot)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
profile.refresh()
}
}
```
### Further reading
In the [Example](/Example) directory you’ll find a complete iOS sample
application that demonstrates how to integrate CombineViewModel into your
application.
## Installation
CombineViewModel is distributed via Swift Package Manager. To add it to your
Xcode project, navigate to File > Add Package Dependency…, paste in the
repository URL, and follow the prompts.

## Bindings
CombineViewModel also provides the complementary [`Bindings`](/Sources/Bindings)
module. It provides two operators — `<~`, the **input binding operator**, and
`~>`, the **output binding operator** — along with various types and protocols
that support it. Note that the concept of a "binding" provided by the Bindings
module is different to [SwiftUI's `Binding` type][Binding].
[Binding]: https://developer.apple.com/documentation/swiftui/binding
Platform-specific binding helpers are also provided:
- [UIKitBindings](/Sources/UIKitBindings)
## Contributing
Have a useful reactive extension in your project?
Please consider contributing it back to the community!
For more details, see the [CONTRIBUTING][] document.
Thank you, [contributors][]!
[CONTRIBUTING]: CONTRIBUTING.md
[contributors]: https://github.com/thoughtbot/Bindings/graphs/contributors
## License
CombineViewModel is Copyright © 2019–20 thoughtbot, inc.
It is free software, and may be redistributed
under the terms specified in the [LICENSE][] file.
[LICENSE]: /LICENSE
## About

CombineViewModel is maintained and funded by thoughtbot, inc.
The names and logos for thoughtbot are trademarks of thoughtbot, inc.
We love open source software!
See [our other projects][community]
or [hire us][hire] to help build your product.
[community]: https://thoughtbot.com/community?utm_source=github
[hire]: https://thoughtbot.com/hire-us?utm_source=github