Last active
June 15, 2022 04:42
-
-
Save alexpiezo/5b4b3a9ffbd32a724b7196a1711e6ff1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Combine | |
import SwiftUI | |
@dynamicMemberLookup public protocol ViewModelBinding { | |
associatedtype State | |
associatedtype Input | |
var state: State { get } | |
mutating func bind(with input: PassthroughSubject<Input, Never>) | |
} | |
public extension ViewModelBinding { | |
subscript<Subject>(dynamicMember keyPath: KeyPath<State, Subject>) -> Subject { | |
state[keyPath: keyPath] | |
} | |
} | |
@propertyWrapper @frozen public struct ViewModel<ObjectType: ViewModelBinding>: DynamicProperty where ObjectType.State: ObservableObject { | |
@dynamicMemberLookup public struct Wrapper { | |
var wrappedObject: ObjectType | |
private var actionSubject: PassthroughSubject<ObjectType.Input, Never> | |
init(wrappedObject: ObjectType) { | |
actionSubject = PassthroughSubject<ObjectType.Input, Never>() | |
self.wrappedObject = wrappedObject | |
self.wrappedObject.bind(with: actionSubject) | |
} | |
subscript<Subject>(dynamicMember keyPath: ReferenceWritableKeyPath<ObjectType.State, Subject>) -> Binding<Subject> { | |
Binding { | |
wrappedObject.state[keyPath: keyPath] | |
} set: { newValue in | |
wrappedObject.state[keyPath: keyPath] = newValue | |
} | |
} | |
public func trigger(_ input: ObjectType.Input) { | |
actionSubject.send(input) | |
} | |
} | |
private var _wrapper: Wrapper | |
private var _object: ObservedObject<ObjectType.State> | |
public var wrappedValue: ObjectType { | |
get { _wrapper.wrappedObject } | |
set { _wrapper.wrappedObject = newValue } | |
} | |
public var projectedValue: Wrapper { | |
_wrapper | |
} | |
public init(wrappedValue: ObjectType) { | |
_wrapper = Wrapper(wrappedObject: wrappedValue) | |
_object = ObservedObject(initialValue: wrappedValue.state) | |
} | |
} | |
// MARK: Implementation | |
class SimpleState: ObservableObject { | |
@Published var text = "1" | |
@Published var text2 = "2" | |
} | |
enum SimpleInput { | |
case action | |
} | |
class SimpleViewModel: ViewModelBinding { | |
var state = SimpleState() | |
private var cancellable = Set<AnyCancellable>() | |
func bind(with input: PassthroughSubject<SimpleInput, Never>) { | |
state.$text.sink { | |
print($0) | |
}.store(in: &cancellable) | |
input | |
.filter { $0 == .action } | |
.sink { _ in | |
self.state.text2 = "3" | |
}.store(in: &cancellable) | |
} | |
} | |
struct ContentView: View { | |
@ViewModel var vm = SimpleViewModel() | |
var body: some View { | |
VStack { | |
Text("Text1 \(vm.text)") | |
Text("Text2 \(vm.text2)") | |
TextField("", text: $vm.text) | |
Button("Save") { | |
$vm.trigger(.action) | |
} | |
} | |
.textFieldStyle(RoundedBorderTextFieldStyle()) | |
.padding(30) | |
} | |
} | |
struct ContentView_Previews: PreviewProvider { | |
static var previews: some View { | |
ContentView() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment