Created
July 21, 2021 02:07
-
-
Save froggomad/782feedf0bf60b04573eb482223a914a 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 UIKit | |
enum StatusType { | |
case information | |
case error(StatusErrorable) | |
} | |
struct StatusError: Error, StatusErrorable { | |
var message: String | |
var instructions: String | |
} | |
class StatusTextFieldView: UIView { | |
// MARK: - Properties - | |
weak var delegate: UITextFieldDelegate? { | |
didSet { | |
textField.delegate = self.delegate | |
} | |
} | |
var type: StatusType { | |
didSet { | |
setupLabels(by: type) | |
} | |
} | |
let exampleText: String? | |
let textFieldPlaceholderText: String? | |
let instructionText: String? | |
// MARK: - Views - | |
lazy var parentStack: UIStackView = .componentStack(elements: [exampleLabel,textField,instructionLabel]) | |
lazy var exampleLabel: UILabel = .captionLabel(text: exampleText) | |
lazy var textField: UITextField = .borderedTextField(placeholderText: textFieldPlaceholderText) | |
lazy var instructionLabel: UILabel = .captionLabel(text: instructionText) | |
init(type: StatusType, exampleText: String? = nil, textFieldPlaceholderText: String? = nil, instructionText: String? = nil) { | |
self.exampleText = exampleText | |
self.textFieldPlaceholderText = textFieldPlaceholderText | |
self.instructionText = instructionText | |
self.type = type | |
super.init(frame: .zero) | |
setupLabels(by: type) | |
setupViews() | |
} | |
private func setupLabels(by type: StatusType) { | |
switch type { | |
case .information: | |
exampleLabel.textColor = .label | |
instructionLabel.textColor = .label | |
case .error: | |
exampleLabel.textColor = .systemRed | |
instructionLabel.textColor = .systemRed | |
} | |
} | |
// MARK: - View Lifecycle - | |
private func setupViews() { | |
addSubview(parentStack) | |
constraints() | |
} | |
private func constraints() { | |
parentStack.translatesAutoresizingMaskIntoConstraints = false | |
NSLayoutConstraint.activate([ | |
parentStack.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 8), | |
parentStack.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor), | |
parentStack.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor), | |
parentStack.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor) | |
]) | |
} | |
func displayErrorMessage(for error: StatusErrorable) { | |
self.type = .error(error) | |
exampleLabel.text = error.message | |
instructionLabel.text = error.instructions | |
} | |
func displayStatusMessage() { | |
self.type = .information | |
exampleLabel.text = exampleText | |
instructionLabel.text = instructionText | |
} | |
required init?(coder: NSCoder) { | |
fatalError("programmatic view") | |
} | |
} | |
class StatusTextField<T: StatusTextFieldDelegatable>: UIControl { | |
weak var statusTextFieldDelegate: T? | |
var type: StatusType | |
var exampleText: String? | |
var textFieldPlaceholderText: String? | |
var instructionText: String? | |
lazy var textFieldView: StatusTextFieldView = StatusTextFieldView(type: type, exampleText: exampleText, textFieldPlaceholderText: textFieldPlaceholderText, instructionText: instructionText) | |
internal init(type: StatusType, exampleText: String? = nil, textFieldPlaceholderText: String? = nil, instructionText: String? = nil) { | |
self.type = type | |
self.exampleText = exampleText | |
self.textFieldPlaceholderText = textFieldPlaceholderText | |
self.instructionText = instructionText | |
super.init(frame: .zero) | |
textFieldView.delegate = statusTextFieldDelegate | |
setupViews() | |
} | |
required init?(coder: NSCoder) { | |
fatalError("programmatic view") | |
} | |
private func setupViews() { | |
addSubview(textFieldView) | |
textFieldView.translatesAutoresizingMaskIntoConstraints = false | |
NSLayoutConstraint.activate([ | |
textFieldView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), | |
textFieldView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor), | |
textFieldView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor), | |
textFieldView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor) | |
]) | |
} | |
func displayErrorMessage(for error: StatusErrorable) { | |
self.textFieldView.displayErrorMessage(for: error) | |
} | |
func displayStatusMessage() { | |
self.textFieldView.displayStatusMessage() | |
} | |
} | |
protocol StatusErrorable: Error { | |
var message: String { get } | |
var instructions: String { get } | |
} | |
protocol StatusTextFieldDelegatable: UITextFieldDelegate { | |
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) | |
associatedtype Error: StatusErrorable | |
} | |
enum PasswordError: StatusErrorable { | |
case invalidPassword | |
var message: String { | |
switch self { | |
case .invalidPassword: | |
return "Invalid Password" | |
} | |
} | |
var instructions: String { | |
switch self { | |
case .invalidPassword: | |
return "Must contain some stuff you didn't put" | |
} | |
} | |
} | |
class StatusTextFieldPasswordDelegate: UIViewController, StatusTextFieldDelegatable { | |
typealias Error = PasswordError | |
weak var textField: StatusTextFieldView? | |
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) { | |
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { | |
// TODO: different delegates can perform different checks based on this information (email validation, password validation, etc) | |
if textField.text == "Foo" { | |
print("entered super secret testing mode, try an \"!\"") | |
if string == "!" { | |
displayMessage(type: .error(Error.invalidPassword)) | |
} | |
} else if textField.text == "Foo!" { | |
displayMessage(type: .information) | |
} | |
return true | |
} | |
} | |
func displayMessage(type: StatusType) { | |
switch type { | |
case let .error(error): | |
textField?.displayErrorMessage(for: error) | |
case .information: | |
textField?.displayStatusMessage() | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment