95 |
96 | @property (nonatomic, strong, nullable) YBInputControlProfile *yb_inputCP;
97 |
98 | @end
99 |
100 |
101 | NS_ASSUME_NONNULL_END
102 |
103 |
104 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # YBInputControl
2 |
3 | iOS文本输入控制-框架:轻松实现文本输入字符类型控制、字符长度控制,侵入性小。
4 | ———— 于 2018-3-14 大规模重构
5 |
6 |
7 | # 使用须知
8 |
9 |
10 | 文本输入解决方案和框架原理讲解请看简书:https://www.jianshu.com/p/0e527df5c1ef
11 |
12 | 首先将`UIView+YBInputControl`的`.h.m`文件拖入工程,在需要使用的地方导入`UIView+YBInputControl.h`
13 |
14 |
15 |
16 | # 具体用法
17 |
18 |
19 | 该框架分别在`UITextField`和`UITextView`的分类中拓展了一个属性:
20 |
21 | @property (nonatomic, strong, nullable) YBInputControlProfile *yb_inputCP;
22 |
23 |
24 | 使用方法就是为该属性赋值。这里通过`YBInputControlProfile`类进行文本控制,控制的具体配置都在该类的属性中。
25 |
26 |
27 | ## UITextField 使用
28 |
29 | ### 常规方法使用:
30 |
31 | YBInputControlProfile *profile = [YBInputControlProfile new];
32 | profile.maxLength = 10;
33 | profile.textControlType = YBTextControlType_excludeInvisible;
34 | [profile addTargetOfTextChange:self action:@selector(textChange:)];
35 | textfield.yb_inputCP = profile;
36 |
37 |
38 | ### 链式语法使用:
39 |
40 | textfield.yb_inputCP = YBInputControlProfile.creat.set_maxLength(10).set_textControlType(YBTextControlType_letter).set_textChanged(^(id obj){
41 | NSLog(@"%@", [obj valueForKey:@"text"]);
42 | });
43 |
44 |
45 | 如你所见,文本变化的回调提供了block闭包形式和添加监听者+SEL的方式。
46 |
47 |
48 | ### 取消功能:
49 |
50 | textfield.yb_inputCP = nil;
51 |
52 |
53 |
54 | ### 设置自己的正则表达式:
55 |
56 | 如果你想使用自己的正则表达式可以使用`YBInputControlProfile`类的这个属性:
57 |
58 | profile.regularStr = @"^[a-z]*$";
59 |
60 |
61 | 若你在设置`textControlType`之后或者唯一设置了`regularStr`,你的正则表达式将会生效,但是你需要注意以下几个问题(当然,若使用`textControlType`配置类型,框架内部会处理下列问题):
62 |
63 | + 注意1:如果不是输入描述性文本的情况下,建议把联想输入关闭(联想输入在输入之前无法监听,无法精确控制输入字符)`textfield.autocorrectionType = UITextAutocorrectionTypeNo;`
64 |
65 | + 注意2:如果不用输入中文,请设置键盘让用户无法切换到中文输入状态(由于中文输入状态自带联想)`textfield.keyboardType = UIKeyboardTypeASCIICapable;`
66 |
67 |
68 | ### 关于侵入性的说明:
69 |
70 | 你仍然可以监听`UITextField`的`delegate`,框架已经做了特殊处理,你可以在任何地方使用:
71 |
72 | textfield.delegate = self;
73 |
74 |
75 | 注意:若实现了如下方法,将覆盖本框架的输入实时限制功能(其他方法可以像往常一样使用):
76 |
77 | - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
78 | //如果你仍然想要框架的实时判断输入功能,需要调用一个共有函数
79 | return yb_shouldChangeCharactersIn(textField, range, string);
80 | }
81 |
82 |
83 |
84 | ## UITextView 使用
85 |
86 | 若不额外设置`UITextView`的`delegate`属性时,使用和`UITextFiled`一样,不需往下看了。
87 |
88 | 若你配置了`UITextView`的`yb_inputCP`属性过后,仍然想要配置`UITextView`的`delegate`。由于`UITextView`的继承特性等一系列复杂的原因,暂时无法减少对其的侵入性。对于`UITextView`来说,当`delegate`是非自身的情况下,`yb_inputCP`属性和`delegate`属性是互斥的,意味着后配置的会覆盖先配置的,因为配置`yb_inputCP`属性时内部是将`UITextView`的`delegate`设置为自身。
89 |
90 | 若你先配置了`yb_inputCP`,后配置:`textView.delegate = otherTarget;`那么该框架的功能将会失效,若你仍然想使其有效,必须实现如下操作:
91 |
92 | - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
93 | return yb_shouldChangeCharactersIn(textView, range, text);
94 | }
95 | - (void)textViewDidChange:(UITextView *)textView {
96 | yb_textDidChange(textView);
97 | }
98 |
99 |
100 | 若你先配置了`textView.delegate = otherTarget;`,后配置`yb_inputCP`,那`otherTarget`对代理方法的回调将全部失效。
101 |
--------------------------------------------------------------------------------
/YBInputControlDemo/ViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.m
3 | // YBInputControlDemo
4 | //
5 | // Created by 杨少 on 2018/3/13.
6 | // Copyright © 2018年 杨波. All rights reserved.
7 | //
8 |
9 | #import "ViewController.h"
10 | #import "UIView+YBInputControl.h"
11 |
12 | @interface ViewController ()
13 |
14 | @end
15 |
16 | @implementation ViewController
17 |
18 | - (void)viewDidLoad {
19 | [super viewDidLoad];
20 |
21 | //以下注意项,框架会自动处理
22 | //* 注意1:如果不是输入描述性文本的情况下,建议把联想输入关闭(联想输入在输入之前无法监听,无法精确控制输入字符)
23 | // .autocorrectionType = UITextAutocorrectionTypeNo;
24 | //* 注意2:如果不用输入中文,请设置键盘让用户无法切换到中文输入状态(由于中文输入状态自带联想)
25 | // .keyboardType = UIKeyboardTypeASCIICapable;
26 |
27 | #pragma mark UITextField 的使用
28 | UITextField *textfield = [UITextField new];
29 | textfield.backgroundColor = [UIColor orangeColor];
30 | textfield.textColor = [UIColor whiteColor];
31 | textfield.frame = CGRectMake(20, 100, [UIScreen mainScreen].bounds.size.width-40, 44);
32 | [self.view addSubview:textfield];
33 |
34 | textfield.delegate = self;
35 | //链式语法使用
36 | textfield.yb_inputCP = YBInputControlProfile.creat.set_maxLength(10).set_textControlType(YBTextControlType_letter).set_textChanged(^(id obj){
37 | NSLog(@"%@", [obj valueForKey:@"text"]);
38 | });
39 |
40 | //常规方法使用
41 | // YBInputControlProfile *profile = [YBInputControlProfile new];
42 | // profile.maxLength = 10;
43 | // profile.textControlType = YBTextControlType_letter;
44 | // [profile addTargetOfTextChange:self action:@selector(textChange:)];
45 | // textfield.yb_inputCP = profile;
46 | // profile.regularStr = @"^[a-z]*$";
47 |
48 | //取消功能
49 | //textfield.yb_inputCP = nil;
50 |
51 | //同样可以按照以往的习惯,设置代理
52 | // textfield.delegate = self;
53 |
54 | //** 特别注意
55 | //在给 textField 设置了非自身的 delegate,若实现了如下方法,将可能覆盖本框架的输入实时限制功能,对应可能会覆盖的函数是:- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string; 解决方法是在其中调用框架方法(若没实现该方法就没什么影响)。
56 |
57 |
58 | #pragma mark UITextView 的使用
59 | UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(20, 200, [UIScreen mainScreen].bounds.size.width-40, 300)];
60 | textView.font = [UIFont systemFontOfSize:14];
61 | textView.backgroundColor = [UIColor orangeColor];
62 | textView.textColor = [UIColor whiteColor];
63 | [self.view addSubview:textView];
64 |
65 | textView.yb_inputCP = YBInputControlProfile.creat.set_textControlType(YBTextControlType_letter).set_maxLength(20).set_textChanged(^(id obj){
66 | NSLog(@"%@", [obj valueForKey:@"text"]);
67 | });
68 |
69 | textView.delegate = self;
70 | //** 特别注意
71 | //对 textView 设置 yb_inputCP 和 delegate 互斥,后配置的将取而代之
72 | //在配置了框架属性 yb_inputCP 过后,给 textView 设置了非自身的 delegate ,将直接覆盖本框架的功能,需要在 - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text 函数 和 - (void)textViewDidChange:(UITextView *)textView 函数中调用框架方法才能有效。
73 | }
74 |
75 | #pragma mark UITextFieldDelegate
76 | //- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
77 | // //如果你仍然想要框架的实时判断输入功能,需要调用一个共有函数
78 | // return yb_shouldChangeCharactersIn(textField, range, string);
79 | //}
80 | - (void)textFieldDidBeginEditing:(UITextField *)textField {
81 | NSLog(@"%@", NSStringFromSelector(_cmd));
82 | }
83 |
84 | #pragma mark UITextViewDelegate
85 | //- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
86 | // return yb_shouldChangeCharactersIn(textView, range, text);
87 | //}
88 | //- (void)textViewDidChange:(UITextView *)textView {
89 | // yb_textDidChange(textView);
90 | //}
91 | - (BOOL)textViewShouldBeginEditing:(UITextView *)textView {
92 | NSLog(@"%@", NSStringFromSelector(_cmd));
93 | return YES;
94 | }
95 |
96 |
97 | #pragma mark event
98 | - (void)textChange:(id)obj {
99 | NSLog(@"%@", [obj valueForKey:@"text"]);
100 | }
101 |
102 |
103 | @end
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/YBInputControlDemo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 48;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 7057FA4B2057D29800C7766F /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7057FA4A2057D29800C7766F /* AppDelegate.m */; };
11 | 7057FA4E2057D29800C7766F /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7057FA4D2057D29800C7766F /* ViewController.m */; };
12 | 7057FA512057D29800C7766F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7057FA4F2057D29800C7766F /* Main.storyboard */; };
13 | 7057FA532057D29800C7766F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7057FA522057D29800C7766F /* Assets.xcassets */; };
14 | 7057FA562057D29800C7766F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7057FA542057D29800C7766F /* LaunchScreen.storyboard */; };
15 | 7057FA592057D29800C7766F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 7057FA582057D29800C7766F /* main.m */; };
16 | 708702852058BB1C0095767D /* UIView+YBInputControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 708702842058BB1C0095767D /* UIView+YBInputControl.m */; };
17 | /* End PBXBuildFile section */
18 |
19 | /* Begin PBXFileReference section */
20 | 7057FA462057D29800C7766F /* YBInputControlDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = YBInputControlDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
21 | 7057FA492057D29800C7766F /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
22 | 7057FA4A2057D29800C7766F /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
23 | 7057FA4C2057D29800C7766F /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; };
24 | 7057FA4D2057D29800C7766F /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; };
25 | 7057FA502057D29800C7766F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
26 | 7057FA522057D29800C7766F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
27 | 7057FA552057D29800C7766F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
28 | 7057FA572057D29800C7766F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
29 | 7057FA582057D29800C7766F /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
30 | 708702832058BB1C0095767D /* UIView+YBInputControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+YBInputControl.h"; sourceTree = ""; };
31 | 708702842058BB1C0095767D /* UIView+YBInputControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+YBInputControl.m"; sourceTree = ""; };
32 | /* End PBXFileReference section */
33 |
34 | /* Begin PBXFrameworksBuildPhase section */
35 | 7057FA432057D29800C7766F /* Frameworks */ = {
36 | isa = PBXFrameworksBuildPhase;
37 | buildActionMask = 2147483647;
38 | files = (
39 | );
40 | runOnlyForDeploymentPostprocessing = 0;
41 | };
42 | /* End PBXFrameworksBuildPhase section */
43 |
44 | /* Begin PBXGroup section */
45 | 7057FA3D2057D29800C7766F = {
46 | isa = PBXGroup;
47 | children = (
48 | 7057FA482057D29800C7766F /* YBInputControlDemo */,
49 | 7057FA472057D29800C7766F /* Products */,
50 | );
51 | sourceTree = "";
52 | };
53 | 7057FA472057D29800C7766F /* Products */ = {
54 | isa = PBXGroup;
55 | children = (
56 | 7057FA462057D29800C7766F /* YBInputControlDemo.app */,
57 | );
58 | name = Products;
59 | sourceTree = "";
60 | };
61 | 7057FA482057D29800C7766F /* YBInputControlDemo */ = {
62 | isa = PBXGroup;
63 | children = (
64 | 708702822058BB1C0095767D /* YBInputControl */,
65 | 7057FA492057D29800C7766F /* AppDelegate.h */,
66 | 7057FA4A2057D29800C7766F /* AppDelegate.m */,
67 | 7057FA4C2057D29800C7766F /* ViewController.h */,
68 | 7057FA4D2057D29800C7766F /* ViewController.m */,
69 | 7057FA4F2057D29800C7766F /* Main.storyboard */,
70 | 7057FA522057D29800C7766F /* Assets.xcassets */,
71 | 7057FA542057D29800C7766F /* LaunchScreen.storyboard */,
72 | 7057FA572057D29800C7766F /* Info.plist */,
73 | 7057FA582057D29800C7766F /* main.m */,
74 | );
75 | path = YBInputControlDemo;
76 | sourceTree = "";
77 | };
78 | 708702822058BB1C0095767D /* YBInputControl */ = {
79 | isa = PBXGroup;
80 | children = (
81 | 708702832058BB1C0095767D /* UIView+YBInputControl.h */,
82 | 708702842058BB1C0095767D /* UIView+YBInputControl.m */,
83 | );
84 | path = YBInputControl;
85 | sourceTree = SOURCE_ROOT;
86 | };
87 | /* End PBXGroup section */
88 |
89 | /* Begin PBXNativeTarget section */
90 | 7057FA452057D29800C7766F /* YBInputControlDemo */ = {
91 | isa = PBXNativeTarget;
92 | buildConfigurationList = 7057FA5C2057D29800C7766F /* Build configuration list for PBXNativeTarget "YBInputControlDemo" */;
93 | buildPhases = (
94 | 7057FA422057D29800C7766F /* Sources */,
95 | 7057FA432057D29800C7766F /* Frameworks */,
96 | 7057FA442057D29800C7766F /* Resources */,
97 | );
98 | buildRules = (
99 | );
100 | dependencies = (
101 | );
102 | name = YBInputControlDemo;
103 | productName = YBInputControlDemo;
104 | productReference = 7057FA462057D29800C7766F /* YBInputControlDemo.app */;
105 | productType = "com.apple.product-type.application";
106 | };
107 | /* End PBXNativeTarget section */
108 |
109 | /* Begin PBXProject section */
110 | 7057FA3E2057D29800C7766F /* Project object */ = {
111 | isa = PBXProject;
112 | attributes = {
113 | LastUpgradeCheck = 0920;
114 | ORGANIZATIONNAME = "杨波";
115 | TargetAttributes = {
116 | 7057FA452057D29800C7766F = {
117 | CreatedOnToolsVersion = 9.2;
118 | ProvisioningStyle = Automatic;
119 | };
120 | };
121 | };
122 | buildConfigurationList = 7057FA412057D29800C7766F /* Build configuration list for PBXProject "YBInputControlDemo" */;
123 | compatibilityVersion = "Xcode 8.0";
124 | developmentRegion = en;
125 | hasScannedForEncodings = 0;
126 | knownRegions = (
127 | en,
128 | Base,
129 | );
130 | mainGroup = 7057FA3D2057D29800C7766F;
131 | productRefGroup = 7057FA472057D29800C7766F /* Products */;
132 | projectDirPath = "";
133 | projectRoot = "";
134 | targets = (
135 | 7057FA452057D29800C7766F /* YBInputControlDemo */,
136 | );
137 | };
138 | /* End PBXProject section */
139 |
140 | /* Begin PBXResourcesBuildPhase section */
141 | 7057FA442057D29800C7766F /* Resources */ = {
142 | isa = PBXResourcesBuildPhase;
143 | buildActionMask = 2147483647;
144 | files = (
145 | 7057FA562057D29800C7766F /* LaunchScreen.storyboard in Resources */,
146 | 7057FA532057D29800C7766F /* Assets.xcassets in Resources */,
147 | 7057FA512057D29800C7766F /* Main.storyboard in Resources */,
148 | );
149 | runOnlyForDeploymentPostprocessing = 0;
150 | };
151 | /* End PBXResourcesBuildPhase section */
152 |
153 | /* Begin PBXSourcesBuildPhase section */
154 | 7057FA422057D29800C7766F /* Sources */ = {
155 | isa = PBXSourcesBuildPhase;
156 | buildActionMask = 2147483647;
157 | files = (
158 | 7057FA4E2057D29800C7766F /* ViewController.m in Sources */,
159 | 7057FA592057D29800C7766F /* main.m in Sources */,
160 | 708702852058BB1C0095767D /* UIView+YBInputControl.m in Sources */,
161 | 7057FA4B2057D29800C7766F /* AppDelegate.m in Sources */,
162 | );
163 | runOnlyForDeploymentPostprocessing = 0;
164 | };
165 | /* End PBXSourcesBuildPhase section */
166 |
167 | /* Begin PBXVariantGroup section */
168 | 7057FA4F2057D29800C7766F /* Main.storyboard */ = {
169 | isa = PBXVariantGroup;
170 | children = (
171 | 7057FA502057D29800C7766F /* Base */,
172 | );
173 | name = Main.storyboard;
174 | sourceTree = "";
175 | };
176 | 7057FA542057D29800C7766F /* LaunchScreen.storyboard */ = {
177 | isa = PBXVariantGroup;
178 | children = (
179 | 7057FA552057D29800C7766F /* Base */,
180 | );
181 | name = LaunchScreen.storyboard;
182 | sourceTree = "";
183 | };
184 | /* End PBXVariantGroup section */
185 |
186 | /* Begin XCBuildConfiguration section */
187 | 7057FA5A2057D29800C7766F /* Debug */ = {
188 | isa = XCBuildConfiguration;
189 | buildSettings = {
190 | ALWAYS_SEARCH_USER_PATHS = NO;
191 | CLANG_ANALYZER_NONNULL = YES;
192 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
193 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
194 | CLANG_CXX_LIBRARY = "libc++";
195 | CLANG_ENABLE_MODULES = YES;
196 | CLANG_ENABLE_OBJC_ARC = YES;
197 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
198 | CLANG_WARN_BOOL_CONVERSION = YES;
199 | CLANG_WARN_COMMA = YES;
200 | CLANG_WARN_CONSTANT_CONVERSION = YES;
201 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
202 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
203 | CLANG_WARN_EMPTY_BODY = YES;
204 | CLANG_WARN_ENUM_CONVERSION = YES;
205 | CLANG_WARN_INFINITE_RECURSION = YES;
206 | CLANG_WARN_INT_CONVERSION = YES;
207 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
208 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
209 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
210 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
211 | CLANG_WARN_STRICT_PROTOTYPES = YES;
212 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
213 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
214 | CLANG_WARN_UNREACHABLE_CODE = YES;
215 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
216 | CODE_SIGN_IDENTITY = "iPhone Developer";
217 | COPY_PHASE_STRIP = NO;
218 | DEBUG_INFORMATION_FORMAT = dwarf;
219 | ENABLE_STRICT_OBJC_MSGSEND = YES;
220 | ENABLE_TESTABILITY = YES;
221 | GCC_C_LANGUAGE_STANDARD = gnu11;
222 | GCC_DYNAMIC_NO_PIC = NO;
223 | GCC_NO_COMMON_BLOCKS = YES;
224 | GCC_OPTIMIZATION_LEVEL = 0;
225 | GCC_PREPROCESSOR_DEFINITIONS = (
226 | "DEBUG=1",
227 | "$(inherited)",
228 | );
229 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
230 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
231 | GCC_WARN_UNDECLARED_SELECTOR = YES;
232 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
233 | GCC_WARN_UNUSED_FUNCTION = YES;
234 | GCC_WARN_UNUSED_VARIABLE = YES;
235 | IPHONEOS_DEPLOYMENT_TARGET = 11.2;
236 | MTL_ENABLE_DEBUG_INFO = YES;
237 | ONLY_ACTIVE_ARCH = YES;
238 | SDKROOT = iphoneos;
239 | };
240 | name = Debug;
241 | };
242 | 7057FA5B2057D29800C7766F /* Release */ = {
243 | isa = XCBuildConfiguration;
244 | buildSettings = {
245 | ALWAYS_SEARCH_USER_PATHS = NO;
246 | CLANG_ANALYZER_NONNULL = YES;
247 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
248 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
249 | CLANG_CXX_LIBRARY = "libc++";
250 | CLANG_ENABLE_MODULES = YES;
251 | CLANG_ENABLE_OBJC_ARC = YES;
252 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
253 | CLANG_WARN_BOOL_CONVERSION = YES;
254 | CLANG_WARN_COMMA = YES;
255 | CLANG_WARN_CONSTANT_CONVERSION = YES;
256 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
257 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
258 | CLANG_WARN_EMPTY_BODY = YES;
259 | CLANG_WARN_ENUM_CONVERSION = YES;
260 | CLANG_WARN_INFINITE_RECURSION = YES;
261 | CLANG_WARN_INT_CONVERSION = YES;
262 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
263 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
264 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
265 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
266 | CLANG_WARN_STRICT_PROTOTYPES = YES;
267 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
268 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
269 | CLANG_WARN_UNREACHABLE_CODE = YES;
270 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
271 | CODE_SIGN_IDENTITY = "iPhone Developer";
272 | COPY_PHASE_STRIP = NO;
273 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
274 | ENABLE_NS_ASSERTIONS = NO;
275 | ENABLE_STRICT_OBJC_MSGSEND = YES;
276 | GCC_C_LANGUAGE_STANDARD = gnu11;
277 | GCC_NO_COMMON_BLOCKS = YES;
278 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
279 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
280 | GCC_WARN_UNDECLARED_SELECTOR = YES;
281 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
282 | GCC_WARN_UNUSED_FUNCTION = YES;
283 | GCC_WARN_UNUSED_VARIABLE = YES;
284 | IPHONEOS_DEPLOYMENT_TARGET = 11.2;
285 | MTL_ENABLE_DEBUG_INFO = NO;
286 | SDKROOT = iphoneos;
287 | VALIDATE_PRODUCT = YES;
288 | };
289 | name = Release;
290 | };
291 | 7057FA5D2057D29800C7766F /* Debug */ = {
292 | isa = XCBuildConfiguration;
293 | buildSettings = {
294 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
295 | CODE_SIGN_STYLE = Automatic;
296 | DEVELOPMENT_TEAM = UU8H9EQ986;
297 | INFOPLIST_FILE = YBInputControlDemo/Info.plist;
298 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
299 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
300 | PRODUCT_BUNDLE_IDENTIFIER = com.yb.YBInputControlDemo;
301 | PRODUCT_NAME = "$(TARGET_NAME)";
302 | TARGETED_DEVICE_FAMILY = "1,2";
303 | };
304 | name = Debug;
305 | };
306 | 7057FA5E2057D29800C7766F /* Release */ = {
307 | isa = XCBuildConfiguration;
308 | buildSettings = {
309 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
310 | CODE_SIGN_STYLE = Automatic;
311 | DEVELOPMENT_TEAM = UU8H9EQ986;
312 | INFOPLIST_FILE = YBInputControlDemo/Info.plist;
313 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
314 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
315 | PRODUCT_BUNDLE_IDENTIFIER = com.yb.YBInputControlDemo;
316 | PRODUCT_NAME = "$(TARGET_NAME)";
317 | TARGETED_DEVICE_FAMILY = "1,2";
318 | };
319 | name = Release;
320 | };
321 | /* End XCBuildConfiguration section */
322 |
323 | /* Begin XCConfigurationList section */
324 | 7057FA412057D29800C7766F /* Build configuration list for PBXProject "YBInputControlDemo" */ = {
325 | isa = XCConfigurationList;
326 | buildConfigurations = (
327 | 7057FA5A2057D29800C7766F /* Debug */,
328 | 7057FA5B2057D29800C7766F /* Release */,
329 | );
330 | defaultConfigurationIsVisible = 0;
331 | defaultConfigurationName = Release;
332 | };
333 | 7057FA5C2057D29800C7766F /* Build configuration list for PBXNativeTarget "YBInputControlDemo" */ = {
334 | isa = XCConfigurationList;
335 | buildConfigurations = (
336 | 7057FA5D2057D29800C7766F /* Debug */,
337 | 7057FA5E2057D29800C7766F /* Release */,
338 | );
339 | defaultConfigurationIsVisible = 0;
340 | defaultConfigurationName = Release;
341 | };
342 | /* End XCConfigurationList section */
343 | };
344 | rootObject = 7057FA3E2057D29800C7766F /* Project object */;
345 | }
346 |
--------------------------------------------------------------------------------
/YBInputControl/UIView+YBInputControl.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+YBInputControl.m
3 | // YBInputLimitDemo
4 | //
5 | // Created by 杨少 on 2018/3/12.
6 | // Copyright © 2018年 yangbo. All rights reserved.
7 | //
8 |
9 | #import "UIView+YBInputControl.h"
10 | #import
11 |
12 | static const void *key_Profile = &key_Profile;
13 | static const void *key_tempDelegate = &key_tempDelegate;
14 |
15 | static BOOL judgeRegular(NSString *contentStr, NSString *regularStr) {
16 | NSError *error;
17 | NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:regularStr options:0 error:&error];
18 | if (error) {
19 | return YES;
20 | }
21 | NSArray *results = [regex matchesInString:contentStr options:0 range:NSMakeRange(0, contentStr.length)];
22 | return results.count > 0;
23 | }
24 | BOOL yb_shouldChangeCharactersIn(id target, NSRange range, NSString *string) {
25 | if (!target) {
26 | return YES;
27 | }
28 | YBInputControlProfile *profile = objc_getAssociatedObject(target, key_Profile);
29 | if (!profile) {
30 | return YES;
31 | }
32 | //计算若输入成功的字符串
33 | NSString *nowStr = [target valueForKey:@"text"];
34 | NSMutableString *resultStr = [NSMutableString stringWithString:nowStr];
35 | if (string.length == 0) {
36 | [resultStr deleteCharactersInRange:range];
37 | } else {
38 | if (range.length == 0) {
39 | [resultStr insertString:string atIndex:range.location];
40 | } else {
41 | [resultStr replaceCharactersInRange:range withString:string];
42 | }
43 | }
44 | //长度判断
45 | if (profile.maxLength != NSUIntegerMax) {
46 | if (!profile.cancelTextLengthControlBefore && resultStr.length > profile.maxLength) {
47 | return NO;
48 | }
49 | }
50 | //正则表达式匹配
51 | if (resultStr.length > 0) {
52 | if (!profile.regularStr || profile.regularStr.length <= 0) {
53 | return YES;
54 | }
55 | return judgeRegular(resultStr, profile.regularStr);
56 | }
57 | return YES;
58 | }
59 | void yb_textDidChange(id target) {
60 | if (!target) {
61 | return;
62 | }
63 | YBInputControlProfile *profile = objc_getAssociatedObject(target, key_Profile);
64 | if (!profile) {
65 | return;
66 | }
67 | //内容适配
68 | if (profile.maxLength != NSUIntegerMax && [target valueForKey:@"markedTextRange"] == nil) {
69 | NSString *resultText = [target valueForKey:@"text"];
70 | if ([target isKindOfClass:UITextView.class]) {
71 | //如果是 UITextView, 不需要过滤空格,未超过最大限制也不要调用 setText: 方法(setText:会导致光标自动移动到尾部)
72 | if (resultText.length > profile.maxLength) {
73 | [target setValue:[resultText substringToIndex:profile.maxLength] forKey:@"text"];
74 | }
75 | } else if ([target isKindOfClass:UITextField.class]) {
76 | //先内容过滤
77 | if (profile.textControlType == YBTextControlType_excludeInvisible) {
78 | resultText = [[target valueForKey:@"text"] stringByReplacingOccurrencesOfString:@" " withString:@""];
79 | }
80 | //再判断长度
81 | if (resultText.length > profile.maxLength) {
82 | [target setValue:[resultText substringToIndex:profile.maxLength] forKey:@"text"];
83 | } else {
84 | [target setValue:resultText forKey:@"text"];
85 | }
86 | }
87 | }
88 | //回调
89 | if (profile.textChangeInvocation) {
90 | [profile.textChangeInvocation setArgument:&target atIndex:2];
91 | [profile.textChangeInvocation invoke];
92 | }
93 | if (profile.textChanged) {
94 | profile.textChanged(target);
95 | }
96 | }
97 |
98 |
99 |
100 | @interface YBInputControlProfile ()
101 | @property (nonatomic, assign) BOOL cancelTextLengthControlBefore;
102 | @property (nonatomic, strong, nullable) NSInvocation *textChangeInvocation;
103 | @end
104 | @implementation YBInputControlProfile
105 | - (instancetype)init
106 | {
107 | self = [super init];
108 | if (self) {
109 | self.maxLength = NSUIntegerMax;
110 | }
111 | return self;
112 | }
113 | - (void)addTargetOfTextChange:(id)target action:(SEL)action {
114 | NSInvocation *invocation = nil;
115 | if (target && action) {
116 | invocation = [NSInvocation invocationWithMethodSignature:[target methodSignatureForSelector:action]];
117 | invocation.target = target;
118 | invocation.selector = action;
119 | }
120 | self.textChangeInvocation = invocation;
121 | }
122 | - (void)setTextControlType:(YBTextControlType)textControlType {
123 | _textControlType = textControlType;
124 |
125 | switch (textControlType) {
126 | case YBTextControlType_none: {
127 | self.regularStr = @"";
128 | self.keyboardType = UIKeyboardTypeDefault;
129 | self.autocorrectionType = UITextAutocorrectionTypeDefault;
130 | self.cancelTextLengthControlBefore = YES;
131 | }
132 | break;
133 | case YBTextControlType_number: {
134 | self.regularStr = @"^[0-9]*$";
135 | self.keyboardType = UIKeyboardTypeNumberPad;
136 | self.autocorrectionType = UITextAutocorrectionTypeNo;
137 | self.cancelTextLengthControlBefore = NO;
138 | }
139 | break;
140 | case YBTextControlType_letter: {
141 | self.regularStr = @"^[a-zA-Z]*$";
142 | self.keyboardType = UIKeyboardTypeASCIICapable;
143 | self.autocorrectionType = UITextAutocorrectionTypeNo;
144 | self.cancelTextLengthControlBefore = NO;
145 | }
146 | break;
147 | case YBTextControlType_letterSmall: {
148 | self.regularStr = @"^[a-z]*$";
149 | self.keyboardType = UIKeyboardTypeASCIICapable;
150 | self.autocorrectionType = UITextAutocorrectionTypeNo;
151 | self.cancelTextLengthControlBefore = NO;
152 | }
153 | break;
154 | case YBTextControlType_letterBig: {
155 | self.regularStr = @"^[A-Z]*$";
156 | self.keyboardType = UIKeyboardTypeASCIICapable;
157 | self.autocorrectionType = UITextAutocorrectionTypeNo;
158 | self.cancelTextLengthControlBefore = NO;
159 | }
160 | break;
161 | case YBTextControlType_number_letter: {
162 | self.regularStr = @"^[0-9a-zA-Z]*$";
163 | self.keyboardType = UIKeyboardTypeASCIICapable;
164 | self.autocorrectionType = UITextAutocorrectionTypeNo;
165 | self.cancelTextLengthControlBefore = NO;
166 | }
167 | break;
168 | case YBTextControlType_number_letterSmall: {
169 | self.regularStr = @"^[0-9a-z]*$";
170 | self.keyboardType = UIKeyboardTypeASCIICapable;
171 | self.autocorrectionType = UITextAutocorrectionTypeNo;
172 | self.cancelTextLengthControlBefore = NO;
173 | }
174 | break;
175 | case YBTextControlType_number_letterBig: {
176 | self.regularStr = @"^[0-9A-Z]*$";
177 | self.keyboardType = UIKeyboardTypeASCIICapable;
178 | self.autocorrectionType = UITextAutocorrectionTypeNo;
179 | self.cancelTextLengthControlBefore = NO;
180 | }
181 | break;
182 | case YBTextControlType_price: {
183 | NSString *tempStr = self.maxLength == NSUIntegerMax?@"":[NSString stringWithFormat:@"%ld", (unsigned long)self.maxLength];
184 | self.regularStr = [NSString stringWithFormat:@"^(([1-9]\\d{0,%@})|0)(\\.\\d{0,2})?$", tempStr];
185 | self.keyboardType = UIKeyboardTypeDecimalPad;
186 | self.autocorrectionType = UITextAutocorrectionTypeNo;
187 | self.cancelTextLengthControlBefore = NO;
188 | }
189 | break;
190 | case YBTextControlType_excludeInvisible: {
191 | self.regularStr = @"";
192 | self.keyboardType = UIKeyboardTypeDefault;
193 | self.autocorrectionType = UITextAutocorrectionTypeDefault;
194 | self.cancelTextLengthControlBefore = YES;
195 | }
196 | break;
197 | default:
198 | break;
199 | }
200 | }
201 | + (YBInputControlProfile *)creat {
202 | YBInputControlProfile *profile = [YBInputControlProfile new];
203 | return profile;
204 | }
205 | - (YBInputControlProfile * _Nonnull (^)(YBTextControlType))set_textControlType {
206 | return ^YBInputControlProfile* (YBTextControlType type) {
207 | self.textControlType = type;
208 | return self;
209 | };
210 | }
211 | - (YBInputControlProfile * _Nonnull (^)(NSString * _Nonnull))set_regularStr {
212 | return ^YBInputControlProfile* (NSString *regularStr) {
213 | self.regularStr = regularStr;
214 | return self;
215 | };
216 | }
217 | - (YBInputControlProfile * _Nonnull (^)(NSUInteger))set_maxLength {
218 | return ^YBInputControlProfile* (NSUInteger maxLength) {
219 | self.maxLength = maxLength;
220 | return self;
221 | };
222 | }
223 | - (YBInputControlProfile * _Nonnull (^)(void (^ _Nonnull)(id _Nonnull)))set_textChanged {
224 | return ^YBInputControlProfile *(void (^block)(id observe)) {
225 | if (block) {
226 | self.textChanged = ^(id observe) {
227 | block(observe);
228 | };
229 | }
230 | return self;
231 | };
232 | }
233 | - (YBInputControlProfile * _Nonnull (^)(id _Nonnull, SEL _Nonnull))set_targetOfTextChange {
234 | return ^YBInputControlProfile *(id target, SEL action) {
235 | [self addTargetOfTextChange:target action:action];
236 | return self;
237 | };
238 | }
239 | @end
240 |
241 |
242 | @interface YBInputControlTempDelegate : NSObject
243 | @property (nonatomic, weak) id delegate_inside;
244 | @property (nonatomic, weak) id delegate_outside;
245 | @property (nonatomic, strong) Protocol *protocol;
246 | @end
247 | @implementation YBInputControlTempDelegate
248 | - (BOOL)respondsToSelector:(SEL)aSelector {
249 | struct objc_method_description des = protocol_getMethodDescription(self.protocol, aSelector, NO, YES);
250 | if (des.types == NULL) {
251 | return [super respondsToSelector:aSelector];
252 | }
253 | if ([self.delegate_inside respondsToSelector:aSelector] || [self.delegate_outside respondsToSelector:aSelector]) {
254 | return YES;
255 | }
256 | return [super respondsToSelector:aSelector];
257 | }
258 | - (void)forwardInvocation:(NSInvocation *)anInvocation {
259 | SEL sel = anInvocation.selector;
260 | BOOL isResponds = NO;
261 | if ([self.delegate_inside respondsToSelector:sel]) {
262 | isResponds = YES;
263 | [anInvocation invokeWithTarget:self.delegate_inside];
264 | }
265 | if ([self.delegate_outside respondsToSelector:sel]) {
266 | isResponds = YES;
267 | [anInvocation invokeWithTarget:self.delegate_outside];
268 | }
269 | if (!isResponds) {
270 | [self doesNotRecognizeSelector:sel];
271 | }
272 | }
273 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
274 | NSMethodSignature *sig_inside = [self.delegate_inside methodSignatureForSelector:aSelector];
275 | NSMethodSignature *sig_outside = [self.delegate_outside methodSignatureForSelector:aSelector];
276 | NSMethodSignature *result_sig = sig_inside?:sig_outside?:nil;
277 | return result_sig;
278 | }
279 | - (Protocol *)protocol {
280 | if (!_protocol) {
281 | if ([self.delegate_inside isKindOfClass:UITextField.self]) {
282 | _protocol = objc_getProtocol("UITextFieldDelegate");
283 | }
284 | }
285 | return _protocol;
286 | }
287 | @end
288 |
289 |
290 | @implementation UITextField (YBInputControl)
291 | #pragma mark insert logic to selector--setDelegate:
292 | + (void)load {
293 | if ([NSStringFromClass(self) isEqualToString:@"UITextField"]) {
294 | Method m1 = class_getInstanceMethod(self, @selector(setDelegate:));
295 | Method m2 = class_getInstanceMethod(self, @selector(customSetDelegate:));
296 | if (m1 && m2) {
297 | method_exchangeImplementations(m1, m2);
298 | }
299 | }
300 | }
301 | - (void)customSetDelegate:(id)delegate {
302 | @synchronized(self) {
303 | if (objc_getAssociatedObject(self, key_Profile)) {
304 | YBInputControlTempDelegate *tempDelegate = [YBInputControlTempDelegate new];
305 | tempDelegate.delegate_inside = self;
306 | if (self.delegate && delegate == self) {
307 | tempDelegate.delegate_outside = self.delegate;
308 | } else {
309 | tempDelegate.delegate_outside = delegate;
310 | }
311 | [self customSetDelegate:tempDelegate];
312 | objc_setAssociatedObject(self, key_tempDelegate, tempDelegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
313 | } else {
314 | [self customSetDelegate:delegate];
315 | }
316 | }
317 | }
318 |
319 | #pragma mark getter setter
320 | - (void)setYb_inputCP:(YBInputControlProfile *)yb_inputCP {
321 | @synchronized(self) {
322 | if (yb_inputCP && [yb_inputCP isKindOfClass:YBInputControlProfile.self]) {
323 | objc_setAssociatedObject(self, key_Profile, yb_inputCP, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
324 |
325 | self.delegate = self;
326 | self.keyboardType = yb_inputCP.keyboardType;
327 | self.autocorrectionType = yb_inputCP.autocorrectionType;
328 | yb_inputCP.textChangeInvocation || yb_inputCP.textChanged ? [self addTarget:self action:@selector(textFieldDidChange:) forControlEvents : UIControlEventEditingChanged]:nil;
329 | } else {
330 | objc_setAssociatedObject(self, key_Profile, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
331 | }
332 | }
333 | }
334 | - (YBInputControlProfile *)yb_inputCP {
335 | return objc_getAssociatedObject(self, key_Profile);
336 | }
337 |
338 | #pragma mark UITextFieldDelegate
339 | - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
340 | UITextRange *selectedRange = [textField markedTextRange];
341 | UITextPosition *position = [textField positionFromPosition:selectedRange.start offset:0];
342 | if (position) return YES;
343 | return yb_shouldChangeCharactersIn(textField, range, string);
344 | }
345 | - (void)textFieldDidChange:(UITextField *)textField {
346 | UITextRange *selectedRange = [textField markedTextRange];
347 | UITextPosition *position = [textField positionFromPosition:selectedRange.start offset:0];
348 | if (position) return;
349 | yb_textDidChange(textField);
350 | }
351 |
352 | @end
353 |
354 |
355 | @implementation UITextView (YBInputControl)
356 |
357 | #pragma mark getter setter
358 | - (void)setYb_inputCP:(YBInputControlProfile *)yb_inputCP {
359 | @synchronized(self) {
360 | if (yb_inputCP && [yb_inputCP isKindOfClass:YBInputControlProfile.self]) {
361 | objc_setAssociatedObject(self, key_Profile, yb_inputCP, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
362 |
363 | self.delegate = self;
364 | self.keyboardType = yb_inputCP.keyboardType;
365 | self.autocorrectionType = yb_inputCP.autocorrectionType;
366 | } else {
367 | objc_setAssociatedObject(self, key_Profile, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
368 | }
369 | }
370 | }
371 | - (YBInputControlProfile *)yb_inputCP {
372 | return objc_getAssociatedObject(self, key_Profile);
373 | }
374 |
375 | #pragma mark UITextViewDelegate
376 | - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
377 | UITextRange *selectedRange = [textView markedTextRange];
378 | UITextPosition *position = [textView positionFromPosition:selectedRange.start offset:0];
379 | if (position) return YES;
380 | return yb_shouldChangeCharactersIn(textView, range, text);
381 | }
382 | - (void)textViewDidChange:(UITextView *)textView {
383 | UITextRange *selectedRange = [textView markedTextRange];
384 | UITextPosition *position = [textView positionFromPosition:selectedRange.start offset:0];
385 | if (position) return;
386 | yb_textDidChange(textView);
387 | }
388 |
389 | @end
390 |
391 |
--------------------------------------------------------------------------------