diff --git a/VDSSample.xcodeproj/project.pbxproj b/VDSSample.xcodeproj/project.pbxproj index 55afba2..227b2f1 100644 --- a/VDSSample.xcodeproj/project.pbxproj +++ b/VDSSample.xcodeproj/project.pbxproj @@ -40,6 +40,7 @@ EA3C3BB528996775000CA526 /* StoryboardInitable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3C3BB128996775000CA526 /* StoryboardInitable.swift */; }; EA3C3BB628996775000CA526 /* MenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3C3BB228996775000CA526 /* MenuViewController.swift */; }; EA3C3BB728996775000CA526 /* ToggleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3C3BB328996775000CA526 /* ToggleViewController.swift */; }; + EAB1D2C528A6B11D00DAE764 /* TestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2C428A6B11D00DAE764 /* TestViewController.swift */; }; EAF7F07C2899698800B287F5 /* Components.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EAF7F07A2899698800B287F5 /* Components.storyboard */; }; EAF7F07D2899698800B287F5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EAF7F07B2899698800B287F5 /* Assets.xcassets */; }; EAF7F09C2899B92400B287F5 /* CheckboxViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F09B2899B92400B287F5 /* CheckboxViewController.swift */; }; @@ -95,6 +96,7 @@ EA3C3BBA289968A0000CA526 /* VDSTypographyTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSTypographyTokens.xcframework; path = ../SharedFrameworks/VDSTypographyTokens.xcframework; sourceTree = ""; }; EA3C3BBB289968A0000CA526 /* VDSFormControlsTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSFormControlsTokens.xcframework; path = ../SharedFrameworks/VDSFormControlsTokens.xcframework; sourceTree = ""; }; EA3C3BC3289968B1000CA526 /* VDS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = VDS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + EAB1D2C428A6B11D00DAE764 /* TestViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestViewController.swift; sourceTree = ""; }; EAF7F07A2899698800B287F5 /* Components.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Components.storyboard; sourceTree = ""; }; EAF7F07B2899698800B287F5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; EAF7F09B2899B92400B287F5 /* CheckboxViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxViewController.swift; sourceTree = ""; }; @@ -207,6 +209,7 @@ EA3C3BB228996775000CA526 /* MenuViewController.swift */, EAF7F11928A14A0E00B287F5 /* RadioButtonViewController.swift */, EA3C3BB328996775000CA526 /* ToggleViewController.swift */, + EAB1D2C428A6B11D00DAE764 /* TestViewController.swift */, ); path = ViewControllers; sourceTree = ""; @@ -358,6 +361,7 @@ EAF7F11A28A14A0E00B287F5 /* RadioButtonViewController.swift in Sources */, EA3C3B9F289966EF000CA526 /* SceneDelegate.swift in Sources */, EA3C3BB428996775000CA526 /* PickerBase.swift in Sources */, + EAB1D2C528A6B11D00DAE764 /* TestViewController.swift in Sources */, EAF7F09C2899B92400B287F5 /* CheckboxViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/VDSSample/ViewControllers/TestViewController.swift b/VDSSample/ViewControllers/TestViewController.swift new file mode 100644 index 0000000..d97aaa1 --- /dev/null +++ b/VDSSample/ViewControllers/TestViewController.swift @@ -0,0 +1,175 @@ +// +// 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) + } +}