// // TestViewController.swift // VDSSample // // Created by Matt Bruce on 8/12/22. // import Foundation import UIKit import Combine import VDS struct User { var firstName: String var lastName: String init(firstName: String, lastName: String) { self.firstName = firstName self.lastName = lastName } } public protocol Bindable { associatedtype ModelType var model: ModelType { get set } var subject: CurrentValueSubject? { get set } func createBinding(with subject: CurrentValueSubject, storeIn subscriptions: inout Set) } extension Bindable { public func send() { subject?.send(model) } } class TextFieldBindingCell: UIView, Bindable { //bindable var model: User var subject: CurrentValueSubject? let firstNameTextField = TextField() let lastNameTextField = TextField() init(model: User) { self.model = model super.init(frame: .zero) translatesAutoresizingMaskIntoConstraints = false firstNameTextField.translatesAutoresizingMaskIntoConstraints = false lastNameTextField.translatesAutoresizingMaskIntoConstraints = false addSubview(firstNameTextField) addSubview(lastNameTextField) print("\(model.firstName) \(model.lastName)") firstNameTextField.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true firstNameTextField.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true firstNameTextField.topAnchor.constraint(equalTo: topAnchor).isActive = true firstNameTextField.heightAnchor.constraint(equalToConstant: 50).isActive = true lastNameTextField.topAnchor.constraint(equalTo: firstNameTextField.bottomAnchor, constant: 20).isActive = true lastNameTextField.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true lastNameTextField.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true lastNameTextField.heightAnchor.constraint(equalToConstant: 50).isActive = true lastNameTextField.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func createBinding(with subject: CurrentValueSubject, storeIn subscriptions: inout Set) { self.model = subject.value self.subject = subject //add the logic let firstNameSubject = CurrentValueSubject(model.firstName) firstNameTextField.createBinding(with: firstNameSubject, storeIn: &subscriptions) firstNameSubject.assign(to: \.model.firstName, on: self).store(in: &subscriptions) firstNameSubject.sink {[weak self] value in self?.send() }.store(in: &subscriptions) let lastNameSubject = CurrentValueSubject(model.lastName) lastNameTextField.createBinding(with: lastNameSubject, storeIn: &subscriptions) lastNameSubject.assign(to: \.model.lastName, on: self).store(in: &subscriptions) lastNameSubject.sink {[weak self] value in guard let self = self else { return } self.send() }.store(in: &subscriptions) } } @objcMembers class TestViewController: UIViewController, Initable { var user = User(firstName: "Joe", lastName: "User") private var subscriptions = Set() required init() { super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white let subject = CurrentValueSubject(user) subject.assign(to: \.user, on: self).store(in: &subscriptions) let nameTextField = TextFieldBindingCell(model: user) nameTextField.createBinding(with: subject, storeIn: &subscriptions) let button = UIButton() button.translatesAutoresizingMaskIntoConstraints = false view.addSubview(nameTextField) view.addSubview(button) nameTextField.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20).isActive = true nameTextField.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20).isActive = true nameTextField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20).isActive = true button.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20).isActive = true button.topAnchor.constraint(equalTo: nameTextField.bottomAnchor, constant: 20).isActive = true button.heightAnchor.constraint(equalToConstant: 50).isActive = true button.widthAnchor.constraint(equalToConstant: 100).isActive = true button.backgroundColor = .blue button.setTitle("Print", for: .normal) button.addAction(UIAction(title: "", handler: { [weak self] action in if let user = self?.user { print("\(user.firstName) \(user.lastName)") } }), for: .touchUpInside) } } extension UITextField { public func textPublisher() -> AnyPublisher { NotificationCenter.default .publisher(for: UITextField.textDidChangeNotification, object: self) .compactMap({ ($0.object as? UITextField)?.text }) .eraseToAnyPublisher() } public func createBinding(with subject: CurrentValueSubject, storeIn subscriptions: inout Set) { subject.sink { [weak self] (value) in if value != self?.text { self?.text = value } }.store(in: &subscriptions) self.textPublisher().sink { (value) in if value != subject.value { subject.send(value) } }.store(in: &subscriptions) } } class TextField: UITextField { var textPadding = UIEdgeInsets( top: 10, left: 20, bottom: 10, right: 20 ) override func textRect(forBounds bounds: CGRect) -> CGRect { layer.borderColor = UIColor.black.cgColor layer.borderWidth = 1 let rect = super.textRect(forBounds: bounds) return rect.inset(by: textPadding) } override func editingRect(forBounds bounds: CGRect) -> CGRect { layer.borderColor = UIColor.black.cgColor layer.borderWidth = 1 let rect = super.editingRect(forBounds: bounds) return rect.inset(by: textPadding) } }