// // MCProxyTextFieldRx.swift // HCQuanfangtong // // Created by Apple on 2022/4/7. // Copyright © 2022 Jyp. All rights reserved. // import Foundation import RxSwift import RxCocoa import UIKit enum KeybordInputType : NSInteger{ case number = 0 //数字 整数位 【最大输入长度】 case decimal //数字强校验 不能0开头 ,可以输入小数 [整数位长度,小数位长度,最大值可以为空] case charater //文本 (不可以输入中文) [最大输入长度] case text //可以输入任何东西 【最大输入长度】 case degital //数字强校验 不能0开头 整数 [最大输入长度,最大值可以为空] case phone //电话号码 case numberOrLetter //数字或26个字母 【最大输入长度】 case chinese //只可以输入中文 } //自动注入 @propertyWrapper class TextFieldWrapper{ /// 输入类型 var type : KeybordInputType /// 最大长度 var maxLength : Int? /// 最大值 var maxValue : Float? /// 小数位 var decimalLength : Int? /// 检验的block var checkBlock : (()->Bool)? /// 结束输入 var endBlock : (()->Void) var defaultValue : UITextField? /// 是否可以输入负数 var limitNagativeNumber : Bool let disposeAutoRelease = DisposeBag() var wrappedValue : UITextField?{ didSet{ wrappedValue?.rx.bindDelegate(delegate: MCTextFieldDelegate.init(type: type, maxLength: maxLength, maxValue: maxValue, decimalLength: decimalLength, limitNagativeNumber: limitNagativeNumber, checkBlock: checkBlock, endBlock: endBlock)).subscribe().disposed(by: disposeAutoRelease) } } init(wrappedValue defaultValue: UITextField? = nil, type:KeybordInputType = .decimal, maxLength: Int? = nil, decimalLength: Int? = nil, maxValue: Float? = nil, limitNagativeNumber: Bool = true, checkBlock: (()->Bool)? = nil, endBlock: @escaping (()->Void)){ self.defaultValue = defaultValue self.maxLength = maxLength self.maxValue = maxValue self.decimalLength = decimalLength self.endBlock = endBlock self.type = type self.limitNagativeNumber = limitNagativeNumber } } @propertyWrapper class TextFieldPhoneWrapper : TextFieldWrapper{ override var wrappedValue : UITextField?{ didSet{ if wrappedValue.isNotNil{ wrappedValue?.rx.bindDelegate(delegate: MCTextFieldDelegate.init(type: type, maxLength: maxLength, maxValue: maxValue, decimalLength: decimalLength, limitNagativeNumber: limitNagativeNumber, checkBlock: checkBlock, endBlock: endBlock)).subscribe().disposed(by: disposeAutoRelease) } } } init(wrappedValue defaultValue: UITextField? = nil, checkBlock: (()->Bool)? = nil, block: @escaping (()->Void)){ super.init(wrappedValue: defaultValue, type: .phone, maxLength: nil, decimalLength: nil, maxValue: nil, limitNagativeNumber: true, checkBlock: checkBlock, endBlock: block) } } @propertyWrapper class TextFieldDecimalWrapper : TextFieldWrapper{ override var wrappedValue : UITextField?{ didSet{ if wrappedValue.isNotNil{ wrappedValue?.rx.bindDelegate(delegate: MCTextFieldDelegate.init(type: type, maxLength: maxLength, maxValue: maxValue, decimalLength: decimalLength, limitNagativeNumber: limitNagativeNumber, checkBlock: checkBlock, endBlock: endBlock)).subscribe().disposed(by: disposeAutoRelease) } } } init(wrappedValue defaultValue: UITextField? = nil, maxValue: Float? = nil, checkBlock: (()->Bool)? = nil, block: @escaping (()->Void)){ super.init(wrappedValue: defaultValue, type: .decimal, maxLength: 9, decimalLength: 2, maxValue: maxValue, limitNagativeNumber: true, checkBlock: checkBlock, endBlock: block) } } @propertyWrapper class TextFieldDegitalWrapper : TextFieldWrapper{ override var wrappedValue : UITextField?{ didSet{ if wrappedValue.isNotNil{ wrappedValue?.rx.bindDelegate(delegate: MCTextFieldDelegate.init(type: type, maxLength: maxLength, maxValue: maxValue, decimalLength: decimalLength, limitNagativeNumber: limitNagativeNumber, checkBlock: checkBlock, endBlock: endBlock)).subscribe().disposed(by: disposeAutoRelease) } } } init(wrappedValue defaultValue: UITextField? = nil, maxLength: Int? = nil, maxValue: Float? = nil, checkBlock: (()->Bool)? = nil, block: @escaping (()->Void)){ super.init(wrappedValue: defaultValue, type: .decimal, maxLength: maxLength, decimalLength: nil, maxValue: maxValue, limitNagativeNumber: true, checkBlock: checkBlock, endBlock: block) } } @propertyWrapper class TextFieldOtherWrapper : TextFieldWrapper{ override var wrappedValue : UITextField?{ didSet{ if wrappedValue.isNotNil{ if self.type == .chinese || self.type == .text{ wrappedValue!.bindChineseDelegate(delegate: MCTextFieldDelegate.chinese(maxLength: maxLength, block: endBlock)) return } wrappedValue?.rx.bindDelegate(delegate: MCTextFieldDelegate.init(type: type, maxLength: maxLength, maxValue: maxValue, decimalLength: decimalLength, limitNagativeNumber: limitNagativeNumber, checkBlock: checkBlock, endBlock: endBlock)).subscribe().disposed(by: disposeAutoRelease) } } } init(wrappedValue defaultValue: UITextField? = nil, type: KeybordInputType, maxLength: Int? = nil, checkBlock: (()->Bool)? = nil, block: @escaping (()->Void)){ super.init(wrappedValue: defaultValue, type: type, maxLength: maxLength, decimalLength: nil, maxValue: nil, limitNagativeNumber: true, checkBlock: checkBlock, endBlock: block) } } class MCTextFieldDelegate : NSObject, UITextFieldDelegate{ /// 输入类型 var type : KeybordInputType /// 最大长度 var maxLength : Int? /// 最大值 var maxValue : Float? /// 小数位数 var decimalLength : Int? /// 是否限制输入负数 var limitNagativeNumber : Bool! /// 检验的block var checkBlock : (()->Bool)? /// 输入结束 var endBlock : (()->Void) init(type:KeybordInputType = .decimal, maxLength: Int? = nil, maxValue: Float? = nil, decimalLength: Int? = nil, limitNagativeNumber: Bool = true, checkBlock: (()->Bool)? = nil, endBlock: @escaping (()->Void)){ self.maxLength = maxLength self.maxValue = maxValue self.decimalLength = decimalLength self.endBlock = endBlock self.checkBlock = checkBlock self.type = type self.limitNagativeNumber = limitNagativeNumber } public static func decimalDefault(maxValue: Float? = nil, checkBlock: (()->Bool)? = nil, block: @escaping (()->Void)) -> MCTextFieldDelegate{ MCTextFieldDelegate.init(type: .decimal, maxLength: 9, maxValue: maxValue, decimalLength: 2, limitNagativeNumber: true, checkBlock: checkBlock, endBlock: block) } public static func phone(checkBlock: (()->Bool)? = nil, block: @escaping (()->Void)) -> MCTextFieldDelegate{ MCTextFieldDelegate.init(type: .phone, checkBlock: checkBlock, endBlock: block) } public static func text(maxLength: Int? = nil, checkBlock: (()->Bool)? = nil, block: @escaping (()->Void)) -> MCTextFieldDelegate{ MCTextFieldDelegate.init(type: .text, maxLength: maxLength, checkBlock: checkBlock, endBlock: block) } public static func charater(maxLength: Int? = nil, checkBlock: (()->Bool)? = nil, block: @escaping (()->Void)) -> MCTextFieldDelegate{ MCTextFieldDelegate.init(type: .charater, maxLength: maxLength, checkBlock: checkBlock, endBlock: block) } public static func degital(maxLength: Int? = nil, maxValue: Float? = nil, checkBlock: (()->Bool)? = nil, block: @escaping (()->Void)) -> MCTextFieldDelegate{ MCTextFieldDelegate.init(type: .degital, maxLength: maxLength, maxValue: maxValue, checkBlock: checkBlock, endBlock: block) } public static func number(maxLength: Int? = nil, maxValue: Float? = nil, checkBlock: (()->Bool)? = nil, block: @escaping (()->Void)) -> MCTextFieldDelegate{ MCTextFieldDelegate.init(type: .number, maxLength: maxLength, maxValue: maxValue, checkBlock: checkBlock, endBlock: block) } public static func numberOrLetter(maxLength: Int? = nil, checkBlock: (()->Bool)? = nil, block: @escaping (()->Void)) -> MCTextFieldDelegate{ MCTextFieldDelegate.init(type: .numberOrLetter, maxLength: maxLength, checkBlock: checkBlock, endBlock: block) } public static func chinese(maxLength: Int? = nil, checkBlock: (()->Bool)? = nil, block: @escaping (()->Void)) -> MCTextFieldDelegate{ MCTextFieldDelegate.init(type: .chinese, maxLength: maxLength, checkBlock: checkBlock, endBlock: block) } var regex : String{ get{ if type == .decimal{ if maxLength.isNil || decimalLength.isNil{ fatalError("配置有问题") } return self.limitNagativeNumber ? "(([0]|(0[.]\\d{0,\(decimalLength!)}))|([1-9]\\d{0,\(maxLength! - 1)}(([.]\\d{0,\(decimalLength!)})?)))?" : "(\\-)?(([0]|(0[.]\\d{0,\(decimalLength!)}))|([1-9]\\d{0,\(maxLength! - 1)}(([.]\\d{0,\(decimalLength!)})?)))?" } if type == .degital{ //纯数字 强效验以0开头 if maxLength.isNotNil{ return self.limitNagativeNumber ? "([1-9]\\d{0,\(maxLength! - 1)})?" : "(\\-)?([1-9]\\d{0,\(maxLength! - 1)})?" } else{ return self.limitNagativeNumber ? "([1-9][0-9]*)?" : "(\\-)?([1-9][0-9]*)?" } } if type == .number{ return maxLength.isNotNil ? "(\\d{0,\(maxLength!)})?" : "([0-9]*)?" } if type == .phone{ return "([1]\\d{0,10})?" } if type == .text{ // return maxLength.isNotNil ? "([\\S\\s]{0,\(maxLength!)})?" : "([\\S\\s]*)?" return "([\\S\\s]*)?" } if type == .charater{ return maxLength.isNotNil ? "([^\\u4e00-\\u9fa5]{0,\(maxLength!)})?" : "([^\\u4e00-\\u9fa5]*)?" } if type == .numberOrLetter{ return maxLength.isNotNil ? "([\\w]{0,\(maxLength!)})?" : "([\\w]*)?" } if type == .chinese{ // return maxLength.isNotNil ? "([\\u4e00-\\u9fa5]{0,\(maxLength!)})?" : "([\\u4e00-\\u9fa5]*)?" return "([\\u4e00-\\u9fa5]*)?" } return "" } } func getRegexStr(_ text: String?) -> String?{ if let text = text{ do { let regularRxpression = try NSRegularExpression.init(pattern: self.regex, options: .caseInsensitive) let result = regularRxpression.matches(in: text, range: NSMakeRange(0, text.count)) var resultArray : [String] = [] var resultStr: String? switch self.type { case .number, .text, .charater, .numberOrLetter, .chinese: for item in result{ resultArray.append(NSString.init(format: "%@", text).substring(with: item.range)) } resultStr = resultArray.joined(separator: "") if self.maxLength.isNotNil{ if (resultStr?.count ?? 0) > self.maxLength! && self.maxLength! > 0{ resultStr = NSString.init(string: resultStr!).substring(with: NSRange.init(location: 0, length: self.maxLength!)) } } if self.type == .number{ if self.maxValue.isNotNil{ if Convert.toString(resultStr, decimalNumber: 0) > self.maxValue!{ resultStr = Convert.toString(self.maxValue, decimalNumber: 0) } } resultStr = Convert.toString(resultStr, decimalNumber: 0) } case .decimal, .degital: for item in result{ resultArray.append(NSString.init(format: "%@", text).substring(with: item.range)) break } resultStr = resultArray.joined(separator: "") if self.maxValue.isNotNil{ if Convert.toString(resultStr, decimalNumber: self.type == .decimal ? self.decimalLength:0) > self.maxValue!{ resultStr = Convert.toString(self.maxValue, decimalNumber: self.type == .decimal ? self.decimalLength:0) } } resultStr = Convert.toString(resultStr, decimalNumber: self.type == .decimal ? self.decimalLength:0) case .phone: for item in result{ resultArray.append(NSString.init(format: "%@", text).substring(with: item.range)) } resultStr = resultArray.joined(separator: "") if (resultStr?.count ?? 0) > 11{ resultStr = NSString.init(string: resultStr!).substring(with: NSRange.init(location: 0, length: 11)) } } return resultStr } catch _ { return nil } } return nil } func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { let toString : String? = (textField.text as NSString?)?.replacingCharacters(in: range, with: string) if self.type == .text || self.type == .chinese{ return true } if toString?.count ?? 0 > 0{ let pricePredicate = NSPredicate.init(format: "SELF MATCHES %@", regex) if !pricePredicate.evaluate(with: toString){ return false } else{ if self.maxValue.isNotNil && (type == .decimal || type == .degital || type == .number){ if Convert.toString(toString, decimalNumber: decimalLength) > self.maxValue!{ return false } } } } return true } func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { if self.checkBlock.isNil{ return true } return self.checkBlock!() } func textFieldDidEndEditing(_ textField: UITextField) { if self.maxValue.isNotNil && (type == .decimal || type == .degital || type == .number){ if Convert.toString(textField.text, decimalNumber: decimalLength) > self.maxValue!{ textField.text = Convert.toString(self.maxValue, decimalNumber: decimalLength) } } if type == .decimal{ textField.text = Convert.toString(textField.text, decimalNumber: decimalLength) // if textField.text.isEmptyStr{ // textField.text = Convert.toString(0, decimalNumber: decimalLength) // } } else if type == .degital || type == .number{ textField.text = Convert.toString(textField.text, decimalNumber: 0) } else{ textField.text = textField.text } self.endBlock() } } extension UITextField{ private struct AssociatedDelegateKey{ static var delegate = "AssociatedDelegateKeyObservables" } private var associatedDelegate : MCTextFieldDelegate?{ set{ objc_setAssociatedObject(self, &AssociatedDelegateKey.delegate, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } get{ objc_getAssociatedObject(self, &AssociatedDelegateKey.delegate) as? MCTextFieldDelegate } } func bindChineseDelegate(delegate: MCTextFieldDelegate){ self.delegate = delegate self.associatedDelegate = delegate self.addTarget(self, action: #selector(mcDelegateConstraintTextFieldDidChange), for: UIControlEvents.editingChanged) } @objc func mcDelegateConstraintTextFieldDidChange(){ guard let delegate = self.delegate as? MCTextFieldDelegate else{ return } if delegate.type == .text || delegate.type == .chinese{ let lang = self.textInputMode?.primaryLanguage if lang == "zh-Hans"{ let textRange = self.markedTextRange if let range = textRange{ if self.position(from: range.start, offset: 0) != nil{ return } } } if let text = self.text{ do { let regularRxpression = try NSRegularExpression.init(pattern: delegate.regex, options: .caseInsensitive) let result = regularRxpression.matches(in: text, range: NSMakeRange(0, text.count)) var resultArray : [String] = [] for item in result{ resultArray.append(NSString.init(format: "%@", text).substring(with: item.range)) } let resultStr = resultArray.joined(separator: "") if delegate.maxLength.isNotNil{ if resultStr.count > (delegate.maxLength ?? 0){ self.text = NSString.init(string: resultStr).substring(with: NSRange.init(location: 0, length: delegate.maxLength ?? 0)) return } } if resultStr == self.text{ return } self.text = resultStr } catch _ { } } } } } extension Reactive where Base:UITextField{ var textDelegate : DelegateProxy{ return TextFieldDelegateProxy.proxy(for: base) } func bindDelegate(delegate:UITextFieldDelegate) ->RxSwift.Observable<[Any]>{ self.base.delegate = delegate return textDelegate.methodInvoked(#selector(UITextFieldDelegate.textField(_:shouldChangeCharactersIn:replacementString:))) } var bindEndEditing : RxSwift.Observable<[Any]>{ get{ return textDelegate.methodInvoked(#selector(UITextFieldDelegate.textFieldDidEndEditing(_:))) } } var bindBeginEditing : RxSwift.Observable<[Any]>{ get{ return textDelegate.methodInvoked(#selector(UITextFieldDelegate.textFieldDidBeginEditing(_:))) } } } public class TextFieldDelegateProxy : DelegateProxy,UITextFieldDelegate,DelegateProxyType{ public static func currentDelegate(for object: UITextField) -> UITextFieldDelegate? { return object.delegate } public static func setCurrentDelegate(_ delegate: UITextFieldDelegate?, to object: UITextField) { object.delegate = delegate } public weak private(set) var textField : UITextField? init(textField : UITextField) { self.textField = textField super.init(parentObject: textField, delegateProxy: TextFieldDelegateProxy.self) } public static func registerKnownImplementations() { self.register { TextFieldDelegateProxy.init(textField: $0) } } public override func setForwardToDelegate(_ delegate: DelegateProxy.Delegate?, retainDelegate: Bool) { super.setForwardToDelegate(delegate, retainDelegate: true) } }