You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
import UIKit
import RxSwift
import RxCocoa
import ObjectiveC
classFormTextField:UIView{structValidationError:Error{letmessage:String}enumRule{case email(errorMessage:String)case range(min:Int, max:Int, errorMessage:String)}enumColor{staticlettitleLabelText=0x999999.color
staticlettitleLabelPlaceholder=0xD0D0D0.color
staticletvalidedLineViewBackground=0xFAC221.color
staticletinvalidedLineViewBackground=0xD8D8D8.color
staticlettextFieldCursor=0xFABF13.color
staticlettextFieldText=0x656162.color
}enumFont{staticlettitleLabel=12.systemFont.regular
staticlettextField=16.systemFont.regular
}enumMetric{staticletitemStackViewTop=8.f
staticletitemStackViewHeight=35.f
staticletlineViewTop=12.f
staticletvalidedLineViewHeight= 1.0.f
staticletinvalidedLineViewHeight= 0.5.f
}fileprivatelettitleLabel=UILabel().then{
$0.numberOfLines =1
$0.textColor =Color.titleLabelText
$0.font =Font.titleLabel
}fileprivatelettextField=UITextField().then{
$0.textColor =Color.textFieldText
$0.tintColor =Color.textFieldCursor
$0.font =Font.textField
}fileprivateletlineView=UIView().then{
$0.backgroundColor =Color.invalidedLineViewBackground
}fileprivateletitemStackView=UIStackView().then{
$0.axis =.horizontal
$0.alignment =.fill
$0.distribution =.fill
$0.spacing =12.f
}fileprivatevarrangeRule:ValidationRuleLength?vardisposeBag=DisposeBag()overridevarintrinsicContentSize:CGSize{varheight=0.f
height +=Font.titleLabel.lineHeight
height +=Metric.itemStackViewTop
height +=Metric.itemStackViewHeight
height +=Metric.lineViewTop
height +=Metric.validedLineViewHeight
returnCGSize(width:UIView.noIntrinsicMetric, height: height)}convenienceinit(title:String, placeholder:String, rules:[Rule]){self.init(frame:.zero)self.titleLabel.text = title
self.textField.placeholder = placeholder
self.textField.delegate =self
for rule in rules {
switch rule {case.email(let errorMessage):letemailValidation=ValidationRulePattern(
pattern:EmailValidationPattern(),
error:ValidationError(message: errorMessage))self.textField.validationRules.add(rule: emailValidation)case.range(let min,let max,let errorMessage):letrangeValidation=ValidationRuleLength(
min: min,
max: max,
lengthType:.characters,
error:ValidationError(message: errorMessage))self.rangeRule = rangeValidation
self.textField.validationRules.add(rule: rangeValidation)}}}overrideinit(frame:CGRect){
super.init(frame: frame)self.addSubview(self.titleLabel)self.addSubview(self.itemStackView)self.itemStackView.addArrangedSubview(self.textField)self.addSubview(self.lineView)self.titleLabel.setContentHuggingPriority(.defaultHigh, for:.vertical)self.titleLabel.snp.makeConstraints{ make in
make.left.top.equalToSuperview()}self.itemStackView.setContentHuggingPriority(.defaultLow, for:.vertical)self.itemStackView.snp.makeConstraints{ make in
make.top.equalTo(self.titleLabel.snp.bottom)
make.left.right.equalToSuperview()
make.height.equalTo(Metric.itemStackViewHeight)}self.lineView.snp.makeConstraints{ make in
make.top.equalTo(self.itemStackView.snp.bottom).offset(Metric.lineViewTop)
make.left.right.equalToSuperview()
make.height.equalTo(Metric.invalidedLineViewHeight)}self.textField.rx.isValided
.distinctUntilChanged().map{ $0 ? Color.validedLineViewBackground :Color.invalidedLineViewBackground }.bind(to:self.lineView.rx.backgroundColor).disposed(by:self.disposeBag)self.textField.rx.isValided
.distinctUntilChanged().map{ $0 ? Metric.validedLineViewHeight :Metric.invalidedLineViewHeight }.subscribe(onNext:{[weak self] height in
guard let `self` =selfelse{return}self.lineView.snp.updateConstraints{ make in
make.height.equalTo(height)}}).disposed(by:self.disposeBag)}requiredinit?(coder aDecoder:NSCoder){fatalError("init(coder:) has not been implemented")}}extensionFormTextField:UITextFieldDelegate{func textField(
_ textField:UITextField,
shouldChangeCharactersIn range:NSRange,
replacementString string:String)->Bool{
guard let text =self.textField.text else{return true }
guard let rangeRule =self.rangeRule else{return true }letnewLength= text.count + string.count - range.length
return newLength <= rangeRule.max
}}
Implement UITextField + Validator
extensionReactivewhere Base:UITextField{varerrors:Observable<[Error]>{returnself.base.rx.text.flatMap{[weak base =self.base] text ->Observable<[Error]>in
guard let base = base else{return.empty()}letresult=Validator.validate(
input: text,
rules: base.validationRules
)
switch result {caselet.invalid(errors):return.just(errors)case.valid:return.empty()}}}varisValided:Observable<Bool>{returnself.base.rx.text.flatMap{[weak base =self.base] text ->Observable<Bool>in
guard let base = base else{return.just(false)}letresult=Validator.validate(
input: text,
rules: base.validationRules
)
switch result {case.invalid:return.just(false)case.valid:return.just(true)}}}}extensionUITextField:AssociatedObjectStore{}privatevarvalidationRuleSetKey="validationRuleSet"extensionUITextField{varvalidationRules:ValidationRuleSet<String>{get{returnself.associatedObject(
forKey:&validationRuleSetKey,
default:ValidationRuleSet<String>())}set{self.setAssociatedObject(newValue, forKey:&validationRuleSetKey)}}}protocolAssociatedObjectStore{}extensionAssociatedObjectStore{func associatedObject<T>(forKey key:UnsafeRawPointer)->T?{returnobjc_getAssociatedObject(self, key)as?T}func associatedObject<T>(forKey key:UnsafeRawPointer, default:@autoclosure()->T)->T{
if let object:T=self.associatedObject(forKey: key){return object
}letobject=`default`()self.setAssociatedObject(object, forKey: key)return object
}func setAssociatedObject<T>(_ object:T?, forKey key:UnsafeRawPointer){objc_setAssociatedObject(self, key, object,.OBJC_ASSOCIATION_RETAIN_NONATOMIC)}}