{"id":19593083,"url":"https://github.com/thoughtbot/combineviewmodel","last_synced_at":"2026-03-13T23:32:16.978Z","repository":{"id":50534564,"uuid":"284782740","full_name":"thoughtbot/CombineViewModel","owner":"thoughtbot","description":"An implementation of the Model-View-ViewModel (MVVM) pattern using Combine.","archived":false,"fork":false,"pushed_at":"2020-12-08T16:15:12.000Z","size":1994,"stargazers_count":60,"open_issues_count":8,"forks_count":4,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-15T13:03:57.030Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thoughtbot.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-08-03T18:56:44.000Z","updated_at":"2025-01-31T17:43:07.000Z","dependencies_parsed_at":"2022-09-19T09:50:22.132Z","dependency_job_id":null,"html_url":"https://github.com/thoughtbot/CombineViewModel","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thoughtbot%2FCombineViewModel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thoughtbot%2FCombineViewModel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thoughtbot%2FCombineViewModel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thoughtbot%2FCombineViewModel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thoughtbot","download_url":"https://codeload.github.com/thoughtbot/CombineViewModel/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251154915,"owners_count":21544571,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-11T08:38:06.035Z","updated_at":"2026-03-13T23:32:16.944Z","avatar_url":"https://github.com/thoughtbot.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# CombineViewModel\n\nAn implementation of the Model-View-ViewModel (MVVM) pattern using Combine.\n\n- [Introduction](#introduction)\n- [Installation](#installation)\n- [Bindings](#bindings)\n- [Contributing](#contributing)\n- [About](#about)\n\n## Introduction\n\nCombineViewModel’s primary goal is to make view updates as easy in UIKit and\nAppKit as they are in SwiftUI.\n\nIn SwiftUI, you write model and view-model classes that conform to Combine’s\n[`ObservableObject`][ObservableObject] protocol. SwiftUI:\n\n1. Observes each model’s `objectWillChange` publisher via the\n   [`@ObservedObject`][ObservedObject] property wrapper, and;\n2. Automatically rerenders the appropriate portion of the view hierarchy.\n\nThe problem with `objectWillChange` _outside_ of SwiftUI is that there's no\nbuilt-in way of achieving (2) — being notified that an object _will_ change is\nnot the same as knowing that it _did_ change and it’s time to update the view.\n\n  [ObservableObject]: https://developer.apple.com/documentation/combine/observableobject\n  [ObservedObject]: https://developer.apple.com/documentation/swiftui/observedobject\n\n### `ObjectDidChangePublisher`\n\nConsider the following sketch of a view model for displaying a user’s social\nnetworking profile:\n\n```swift\n// ProfileViewModel.swift\n\nimport CombineViewModel\nimport UIKit\n\nclass ProfileViewModel: ObservableObject {\n  @Published var profileImage: UIImage?\n  @Published var topPosts: [Post]\n\n  func refresh() {\n    // Request updated profile info from the server.\n  }\n}\n```\n\nWith CombineViewModel, you can subscribe to _did change_ notifications using\nthe `observe(on:)` operator:\n\n```swift\nlet profile = ProfileViewModel()\n\nprofileSubscription = profile.observe(on: DispatchQueue.main).sink { profile in\n  // Called on the main queue when either (or both) of `profileImage`\n  // or `topPosts` have changed.\n}\n\nprofile.refresh()\n```\n\n### Automatic view updates\n\nBuilding on `ObjectDidChangePublisher` is the `ViewModelObserver` protocol and\n`@ViewModel` property wrapper. Instead of manually managing the\n`ObjectDidChangePublisher` subscription like above, we can have it managed\nautomatically:\n\n```swift\n// ProfileViewController.swift\n\nimport CombineViewModel\nimport UIKit\n\n// 1️⃣ Conform your view controller to the ViewModelObserver protocol.\nclass ProfileViewController: UITableViewController, ViewModelObserver {\n  enum Section: Int {\n    case topPosts\n  }\n\n  @IBOutlet private var profileImageView: UIImageView!\n  private var dataSource: UITableViewDiffableDataSource\u003cSection, Post\u003e!\n\n  // 2️⃣ Declare your view model using the `@ViewModel` property wrapper.\n  @ViewModel private var profile: ProfileViewModel\n\n  // 3️⃣ Initialize your view model in init().\n  required init?(profile: ProfileViewModel, coder: NSCoder) {\n    super.init(coder: coder)\n    self.profile = profile\n  }\n\n  // 4️⃣ The `updateView()` method is automatically called on the main queue\n  //     when the view model changes. It is always called after `viewDidLoad()`.\n  func updateView() {\n    profileImageView.image = profile.profileImage\n\n    var snapshot = NSDiffableDataSourceSnapshot\u003cSection, Post\u003e()\n    snapshot.appendSections([.topPosts])\n    snapshot.appendItems(profile.topPosts)\n    dataSource.apply(snapshot)\n  }\n\n  override func viewWillAppear(_ animated: Bool) {\n    super.viewWillAppear(animated)\n\n    profile.refresh()\n  }\n}\n```\n\n### Further reading\n\nIn the [Example](/Example) directory you’ll find a complete iOS sample\napplication that demonstrates how to integrate CombineViewModel into your\napplication.\n\n## Installation\n\nCombineViewModel is distributed via Swift Package Manager. To add it to your\nXcode project, navigate to File \u003e Add Package Dependency…, paste in the\nrepository URL, and follow the prompts.\n\n\u003cimg alt=\"Screen capture of Xcode on macOS Big Sur, with the Add Package Dependency menu item highlighted\" width=\"945\" src=\"/Documentation/Images/add-package-dependency.png\"\u003e\n\n## Bindings\n\nCombineViewModel also provides the complementary [`Bindings`](/Sources/Bindings)\nmodule. It provides two operators — `\u003c~`, the **input binding operator**, and\n`~\u003e`, the **output binding operator** — along with various types and protocols\nthat support it. Note that the concept of a \"binding\" provided by the Bindings\nmodule is different to [SwiftUI's `Binding` type][Binding].\n\n  [Binding]: https://developer.apple.com/documentation/swiftui/binding\n\nPlatform-specific binding helpers are also provided:\n\n- [UIKitBindings](/Sources/UIKitBindings)\n\n## Contributing\n\nHave a useful reactive extension in your project?\nPlease consider contributing it back to the community!\n\nFor more details, see the [CONTRIBUTING][] document.\nThank you, [contributors][]!\n\n  [CONTRIBUTING]: CONTRIBUTING.md\n  [contributors]: https://github.com/thoughtbot/Bindings/graphs/contributors\n\n## License\n\nCombineViewModel is Copyright © 2019–20 thoughtbot, inc.\nIt is free software, and may be redistributed\nunder the terms specified in the [LICENSE][] file.\n\n  [LICENSE]: /LICENSE\n\n## About\n\n![thoughtbot](http://presskit.thoughtbot.com/images/thoughtbot-logo-for-readmes.svg)\n\nCombineViewModel is maintained and funded by thoughtbot, inc.\nThe names and logos for thoughtbot are trademarks of thoughtbot, inc.\n\nWe love open source software!\nSee [our other projects][community]\nor [hire us][hire] to help build your product.\n\n  [community]: https://thoughtbot.com/community?utm_source=github\n  [hire]: https://thoughtbot.com/hire-us?utm_source=github\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthoughtbot%2Fcombineviewmodel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthoughtbot%2Fcombineviewmodel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthoughtbot%2Fcombineviewmodel/lists"}