// // TestViewController.swift // VDSSample // // Created by Matt Bruce on 8/12/22. // import Foundation import UIKit import Combine class User: ObservableObject { var firstName: String var lastName: String init(firstName: String, lastName: String) { self.firstName = firstName self.lastName = lastName } } class TextFieldCell: UIView { let firstNameTextField = TextField() let lastNameTextField = TextField() var subject: CurrentValueSubject private var subscriptions = Set() var model: User private func send() { subject.send(model) } init(subject: CurrentValueSubject) { self.model = subject.value self.subject = subject 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 //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) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } @objcMembers class TestViewController: UIViewController, Initable { var user = User(firstName: "Joe", lastName: "User") var nameTextField: TextFieldCell! var button = UIButton() private var cancellables = 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: &cancellables) nameTextField = TextFieldCell(subject: subject) 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 { func textPublisher() -> AnyPublisher { NotificationCenter.default .publisher(for: UITextField.textDidChangeNotification, object: self) .compactMap({ ($0.object as? UITextField)?.text }) .eraseToAnyPublisher() } 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) } }