-
-
Save hsleedevelop/96d6190a8a64e215b6b9654583d86cf9 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