https://github.com/kk-vv/mvvm
Use Combine implement MVVM in UIKit
https://github.com/kk-vv/mvvm
combine mvvm swift uikit
Last synced: 3 months ago
JSON representation
Use Combine implement MVVM in UIKit
- Host: GitHub
- URL: https://github.com/kk-vv/mvvm
- Owner: kk-vv
- Created: 2025-02-27T11:11:54.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2025-02-27T11:38:35.000Z (3 months ago)
- Last Synced: 2025-02-27T16:03:07.477Z (3 months ago)
- Topics: combine, mvvm, swift, uikit
- Language: Swift
- Homepage:
- Size: 407 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: readme.md
Awesome Lists containing this project
README
## Swift MVVM with Combine in UIKit
> Use Combine implement MVVM in UIKIT
#### Screenshot
|Image|
|--------|
||#### ViewModel
```Swift
import Foundation
import Combinetypealias ActivityState = Result
protocol ViewModelInputs {
func onSearch(by name: String?)
func toggleMark(by trackId: Int)
}protocol ViewModelOutputs {
var viewState: AnyPublisher { get }
var activityState: AnyPublisher { get }
}protocol ViewModelType {
var inputs: ViewModelInputs { get }
var outputs: ViewModelOutputs { get }
}class ViewModel {
private let searchInput: PassthroughSubject = .init()
private let toggleMarkInput: PassthroughSubject = .init()
private let service: AppStoreService
private let backingViewState: CurrentValueSubject
private let backingActivityState: CurrentValueSubject = .init(.success(false))
private var cancellables: Set = []
init(service: AppStoreService = .init()) {
self.service = service
backingViewState = .init(.init())
bindInputs()
}
private func bindInputs() {
searchInput.compactMap { $0 }
.filter { !$0.isEmpty }
.throttle(for: .milliseconds(200), scheduler: DispatchQueue.main, latest: true)
.sink { [weak self] term in
self?.handleSearchAction(term: term)
}
.store(in: &cancellables)
toggleMarkInput
.sink { [weak self] trackId in
self?.setViewState {
$0.toggleMarked(by: trackId)
}
}
.store(in: &cancellables)
}
private func handleSearchAction(term: String) {
self.backingActivityState.send(.success(true))
service.searchApp(term: term)
.sink { [weak self] event in
self?.backingActivityState.send(.success(false))
switch event {
case .finished: ()
case .failure(let error):
self?.backingActivityState.send(.failure(error))
}
} receiveValue: { [weak self] apps in
self?.setViewState {
$0.reloadApps(apps)
}
}
.store(in: &cancellables)
}
private func setViewState(_ setter: (inout ViewState) -> Void) {
var viewState = backingViewState.value
setter(&viewState)
backingViewState.send(viewState)
}
}extension ViewModel: ViewModelInputs {
func onSearch(by name: String?) {
searchInput.send(name)
}
func toggleMark(by trackId: Int) {
toggleMarkInput.send(trackId)
}
}extension ViewModel: ViewModelOutputs {
var viewState: AnyPublisher {
backingViewState.eraseToAnyPublisher()
}
var activityState: AnyPublisher, Never> {
backingActivityState.eraseToAnyPublisher()
}
}extension ViewModel: ViewModelType {
var inputs: ViewModelInputs { self }
var outputs: ViewModelOutputs { self }
}
```#### Bind Outputs in ViewController
```Swift
private func bindOutputs() {
viewModel.outputs.activityState
.receive(on: DispatchQueue.main)
.sink { [weak self] event in
switch event {
case .success(let isLoading):
self?.searchButton.isHidden = isLoading
if isLoading {
self?.activityIndicator.startAnimating()
} else {
self?.activityIndicator.stopAnimating()
}
case .failure(let error):
self?.searchButton.isHidden = false
print("Error:", error.localizedDescription)
}
}
.store(in: &cancellables)
viewModel.outputs.viewState
.receive(on: DispatchQueue.main)
.removeDuplicates()
.sink { [weak self] viewState in
self?.updateData(viewState)
}
.store(in: &cancellables)
}
```#### Trigger Inputs in ViewController
```Swift
@objc
private func onSearchAction() {
view.endEditing(true)
viewModel.inputs.onSearch(by: searchBar.text)
}
```