├── README.md ├── SDTextLimit.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── slowdony.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── slowdony.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── SDTextLimit ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── SDBaseTextView.h ├── SDBaseTextView.m ├── SDTextLimitTool.h ├── SDTextLimitTool.m ├── SDTextViewController.h ├── SDTextViewController.m ├── ViewController.h ├── ViewController.m └── main.m ├── SDTextLimitTests ├── Info.plist └── SDTextLimitTests.m └── SDTextLimitUITests ├── Info.plist └── SDTextLimitUITests.m /README.md: -------------------------------------------------------------------------------- 1 | # SDTextLimit 2 | 3 | ### 一.`textFiled/textView`限制字符长度 4 | 一开始我在做UITextFiled和UITextView限制字数 5 | 1.TextFiled限制字数 6 | `[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(textFiledChange:) name:UITextFieldTextDidChangeNotification object:nil];` 7 | 或者 8 | `[textField addTarget:self action:@selector(textFiledChange:) forControlEvents:UIControlEventEditingChanged];` 9 | 然后在 10 | 11 | ``` 12 | -(void)textFiledChange:(UITextFiled *)textFiled{ 13 | 14 | if(textFiled.text.length > maxNumber) 15 | { 16 | textField.text= [textFiled.text substringToIndex:maxNumber]; 17 | } 18 | 19 | } 20 | ``` 21 | 也可以在textField的代理中做相应的处理 22 | ``` 23 | - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {} //具体实现和上面方法一样就不多说了 24 | ``` 25 | 26 | 2.TextView限制字数 27 | 在TextView的代理中 28 | ``` 29 | - (void)textViewDidChange:(UITextView *)textView{ 30 | if(textView.text.length > maxNumber) 31 | { 32 | textView.text= [textView.text substringToIndex:maxNumber]; 33 | } 34 | } 35 | ``` 36 | 以上方法是最简单的处理,如果输入英文字符完全没有问题,但是输入中文时,如果使用第三方键盘也是没有问题的,但就是在系统自带键盘输入拼音时,你就会发现有很严重的问题😂,当你输入一个拼音时,你还没有选中你要选的文字时,已经被键盘当做字母输入到`textFiled/textView`中了,这并不是你想要的结果...比如你用系统键盘输入"你好",它会把n i h a o显示在`textFiled/textView`中, 不但没有输入汉字,还每个字母占两个字符长度... 37 | 38 | 分析一下当你输入拼音时,此时你输入的拼音还是处于高亮状态,这时已经走截取字符的方法,截取完又赋值给`textFiled/textView`,所以就会出现以上的问题,处理方法就是监听系统键盘输入拼音处于高亮状态时不截取字符 39 | ``` 40 | /** 41 | textFiled限制字数 42 | */ 43 | - (void)textFiledChange:(UITextField *)textField{ 44 | NSInteger maxNumber = 15; 45 | NSString *toBeString = textField.text; 46 | NSString *lang = textField.textInputMode.primaryLanguage; // 键盘输入模式 47 | if([lang isEqualToString:@"zh-Hans"]) { //简体中文输入,包括简体拼音,健体五笔,简体手写 48 | UITextRange *selectedRange = [textField markedTextRange]; 49 | //获取高亮部分 50 | UITextPosition *position = [textField positionFromPosition:selectedRange.start offset:0]; 51 | //没有高亮选择的字,则对已输入的文字进行字数统计和限制 52 | if(!position) { 53 | 54 | if(toBeString.length > maxNumber) { 55 | textField.text = [toBeString substringToIndex:maxNumber]; 56 | } 57 | } else{ //有高亮选择的字符串,则暂不对文字进行统计和限制 58 | 59 | } 60 | } 61 | else{ //中文输入法以外的直接对其统计限制即可,不考虑其他语种情况 62 | MyLog(@"haha "); 63 | 64 | if(toBeString.length > maxNumber) { 65 | // textField.text= [toBeString substringToIndex:maxNumber]; 66 | //3月19日更新 67 | //防止表情被截段 68 | textField.text = [[self class] subStringWith:toBeString index:maxNumber]; 69 | } 70 | } 71 | } 72 | //textView同上处理 73 | ``` 74 | 完美解决使用系统键盘输入汉字出现的截取拼音字母显示在`textFiled/textView`中 75 | 76 | 以上处理一般`textFiled/textView`限制字数长度 77 | 78 | ### 二.`textFiled/textView`限制输入特殊字符 79 | 80 | ``` 81 | /** 82 | 限制输入emoji表情 83 | */ 84 | + (NSString *)disable_emoji:(NSString *)text{ 85 | NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[^\\u0020-\\u007E\\u00A0-\\u00BE\\u2E80-\\uA4CF\\uF900-\\uFAFF\\uFE30-\\uFE4F\\uFF00-\\uFFEF\\u0080-\\u009F\\u2000-\\u201f\r\n]"options:NSRegularExpressionCaseInsensitive error:nil]; 86 | NSString *modifiedString = [regex stringByReplacingMatchesInString:text 87 | options:0 88 | range:NSMakeRange(0, [text length]) 89 | withTemplate:@""]; 90 | return modifiedString; 91 | } 92 | 93 | ``` 94 | ``` 95 | /** 96 | 只能输入汉字,数字,英文,括号,下划线,横杠,空格 97 | */ 98 | +(NSString *)filterCharactors:(NSString *)string{ 99 | 100 | NSString *regular = @"[^a-zA-Z0-9()_\u4E00-\u9FA5\\s-]"; //根据你不同的需求填写你自己的正则 101 | NSString *str = [[self class] filterCharactor:string withRegex:[NSString stringWithFormat:@"%@",regular]]; 102 | return str; 103 | } 104 | ``` 105 | 原理是当字符串判断是否输入你要限制的字符,把要限制的字符截掉 106 | 但是当你的`textFiled/textView`既要限制字数,又要限制输入特殊字符,把上面两个方法调完,你会发现,第一个问题又出来了,而且这次是第一次用系统键盘输入汉字时正常,如果你要继续在输入时依然会把拼音当成字符输入到`textFiled/textView`里.所以对上面的方法再次进行优化 107 | ### 三.`textFiled/textView`限制字符长度并且限制输入特殊字符 108 | ``` 109 | /** 110 | 判断NSString中是否有表情 111 | */ 112 | + (BOOL)isContainsEmoji:(NSString *)string { 113 | __block BOOL isEomji = NO; 114 | [string enumerateSubstringsInRange:NSMakeRange(0, [string length]) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { 115 | const unichar hs = [substring characterAtIndex:0]; 116 | // surrogate pair 117 | if (0xd800 <= hs && hs <= 0xdbff) { 118 | if (substring.length > 1) { 119 | const unichar ls = [substring characterAtIndex:1]; 120 | const int uc = ((hs - 0xd800) * 0x400) + (ls - 0xdc00) + 0x10000; 121 | if (0x1d000 <= uc && uc <= 0x1f77f) { 122 | isEomji = YES; 123 | } 124 | } 125 | } else { 126 | // non surrogate 127 | if (0x2100 <= hs && hs <= 0x27ff && hs != 0x263b) { 128 | if (!(9312 <= hs && hs <= 9327)) { // 9312代表① 表示①至⑯ 129 | isEomji = YES; 130 | } 131 | } else if (0x2B05 <= hs && hs <= 0x2b07) { 132 | isEomji = YES; 133 | } else if (0x2934 <= hs && hs <= 0x2935) { 134 | isEomji = YES; 135 | } else if (0x3297 <= hs && hs <= 0x3299) { 136 | isEomji = YES; 137 | } else if (hs == 0xa9 || hs == 0xae || hs == 0x303d || hs == 0x3030 || hs == 0x2b55 || hs == 0x2b1c || hs == 0x2b1b || hs == 0x2b50|| hs == 0x231a ) { 138 | isEomji = YES; 139 | } 140 | if (!isEomji && substring.length > 1) { 141 | const unichar ls = [substring characterAtIndex:1]; 142 | if (ls == 0x20e3) { 143 | isEomji = YES; 144 | } 145 | } 146 | } 147 | }]; 148 | return isEomji; 149 | } 150 | ``` 151 | ``` 152 | /** 153 | 判断是否存在特殊字符 只能输入汉字,数字,英文,括号,下划线,横杠,空格 154 | */ 155 | + (BOOL)isContainsSpecialCharacters:(NSString *)searchText 156 | { 157 | /*此方法也可以理论上可以,但实测还是会有问题*/ 158 | // NSString *regex = @"[^a-zA-Z0-9()_\u4E00-\u9FA5\\s-]"; 159 | // NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex]; 160 | // BOOL isValid = [predicate evaluateWithObject:string]; 161 | // return isValid; 162 | 163 | NSError *error = NULL; 164 | NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[^a-zA-Z0-9()_\u4E00-\u9FA5\\s-]" options:NSRegularExpressionCaseInsensitive error:&error]; 165 | NSTextCheckingResult *result = [regex firstMatchInString:searchText options:0 range:NSMakeRange(0, [searchText length])]; 166 | if (result) { 167 | return YES; 168 | }else { 169 | return NO; 170 | } 171 | 172 | } 173 | 174 | ``` 175 | ``` 176 | /** 177 | textFiled限制字数 178 | */ 179 | + (void)restrictionInputTextField:(UITextField *)textField maxNumber:(NSInteger)maxNumber{ 180 | 181 | NSString *toBeString = textField.text; 182 | NSString *lang = textField.textInputMode.primaryLanguage; // 键盘输入模式 183 | if([lang isEqualToString:@"zh-Hans"]) { //简体中文输入,包括简体拼音,健体五笔,简体手写 184 | UITextRange *selectedRange = [textField markedTextRange]; 185 | //获取高亮部分 186 | UITextPosition *position = [textField positionFromPosition:selectedRange.start offset:0]; 187 | //没有高亮选择的字,则对已输入的文字进行字数统计和限制 188 | if(!position) { 189 | 190 | if(toBeString.length > maxNumber) { 191 | textField.text = [toBeString substringToIndex:maxNumber]; 192 | } 193 | } else{ //有高亮选择的字符串,则暂不对文字进行统计和限制 194 | 195 | } 196 | } 197 | else{ //中文输入法以外的直接对其统计限制即可,不考虑其他语种情况 198 | MyLog(@"haha "); 199 | 200 | if(toBeString.length > maxNumber) { 201 | //textField.text= [toBeString substringToIndex:maxNumber]; 202 |            //3月19日更新 203 | //防止表情被截段 204 | textField.text = [[self class] subStringWith:toBeString index:maxNumber]; 205 |          } 206 | } 207 | 208 | } 209 | ``` 210 | 最终方法 211 | ``` 212 | /** 213 | 除去特殊字符并限制字数的textFiled 214 | */ 215 | + (void)restrictionInputTextFieldMaskSpecialCharacter:(UITextField *)textField maxNumber:(NSInteger)maxNumber{ 216 | 217 | if ([[self class]isContainsEmoji:textField.text]){ 218 | textField.text = [[self class]disable_emoji:textField.text]; 219 | return; 220 | } 221 | if ([[self class]isContainsSpecialCharacters:textField.text]){ 222 | textField.text = [[self class]filterCharactors:textField.text]; 223 | return; 224 | } 225 | [[self class]restrictionInputTextField:textField maxNumber:maxNumber]; 226 | } 227 | ``` 228 | UITextView同上方法 229 | 230 | 最最后...... 231 | 232 | 233 | 最近测试妹子给测出一个问题,就是我的一个输入框是填写电话的,我的 `keyboardType` 是` UIKeyboardTypePhonePad`.理论上只能输入数字了,于是我也就偷懒没做任何判断屏蔽特殊字符,但是依然给我测出可以输入emoji表情和一些特殊字符,🤣,然后我就想应该是在其他地方复制粘贴到我的`textFiled`里吧,于是我就想到很粗鲁的方法,就是把`textFiled`的粘贴给禁掉🤔 哈哈,以下方法不建议采用,还是老老实实的吧限制特殊字符吧,但是你非要和我一样懒😂,那我也管不着🙄 234 | ``` 235 | //屏蔽textFiled粘贴 236 | //Initialzation code 237 | UILongPressGestureRecognizer *longRecognizer = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(addGestureRecognizer:)]; 238 | longRecognizer.allowableMovement = 100.0f; 239 | longRecognizer.minimumPressDuration = 1.0; 240 | [self addGestureRecognizer:longRecognizer]; 241 | 242 | //重写 243 | - (BOOL)canPerformAction:(SEL)action withSender:(id)sender 244 | { 245 | UIMenuController *menuController = [UIMenuController sharedMenuController]; 246 | if (menuController) { 247 | //设置为不可用 248 | [UIMenuController sharedMenuController].menuVisible = NO; 249 | } 250 | return NO; 251 | } 252 | 253 | 254 | - (void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 255 | { 256 | //Prevent zooming but not panning 257 | if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) 258 | { 259 | gestureRecognizer.enabled = NO; 260 | } 261 | [super addGestureRecognizer:gestureRecognizer]; 262 | } 263 | 264 | ``` 265 | 266 | 267 | 如果你还有其他好的方法欢迎提出,共同学习,peace! 268 | 269 | 参考[这篇文章](http://zeeyang.com/2015/09/17/TextField_count_limit_handle/) 270 | 271 | # 3月19更新 272 | 273 | ``` 274 | //防止原生emoji表情被截断 275 | + (NSString *)subStringWith:(NSString *)string index:(NSInteger)index{ 276 | 277 | NSString *result = string; 278 | if (result.length > index) { 279 | NSRange rangeIndex = [result rangeOfComposedCharacterSequenceAtIndex:index]; 280 | result = [result substringToIndex:(rangeIndex.location)]; 281 | } 282 | 283 | return result; 284 | } 285 | ``` 286 | 287 | 288 | -------------------------------------------------------------------------------- /SDTextLimit.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | EF2A4FEC2014D501009F3B65 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = EF2A4FEB2014D501009F3B65 /* AppDelegate.m */; }; 11 | EF2A4FEF2014D501009F3B65 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = EF2A4FEE2014D501009F3B65 /* ViewController.m */; }; 12 | EF2A4FF22014D501009F3B65 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EF2A4FF02014D501009F3B65 /* Main.storyboard */; }; 13 | EF2A4FF42014D501009F3B65 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EF2A4FF32014D501009F3B65 /* Assets.xcassets */; }; 14 | EF2A4FF72014D501009F3B65 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EF2A4FF52014D501009F3B65 /* LaunchScreen.storyboard */; }; 15 | EF2A4FFA2014D501009F3B65 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = EF2A4FF92014D501009F3B65 /* main.m */; }; 16 | EF2A50042014D501009F3B65 /* SDTextLimitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = EF2A50032014D501009F3B65 /* SDTextLimitTests.m */; }; 17 | EF2A500F2014D501009F3B65 /* SDTextLimitUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = EF2A500E2014D501009F3B65 /* SDTextLimitUITests.m */; }; 18 | EF2A50212014DCBD009F3B65 /* SDTextLimitTool.m in Sources */ = {isa = PBXBuildFile; fileRef = EF2A50202014DCBD009F3B65 /* SDTextLimitTool.m */; }; 19 | EF2A50242014DF38009F3B65 /* SDBaseTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = EF2A50232014DF38009F3B65 /* SDBaseTextView.m */; }; 20 | EF2A502D2014E1CA009F3B65 /* SDTextViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = EF2A502C2014E1CA009F3B65 /* SDTextViewController.m */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXContainerItemProxy section */ 24 | EF2A50002014D501009F3B65 /* PBXContainerItemProxy */ = { 25 | isa = PBXContainerItemProxy; 26 | containerPortal = EF2A4FDF2014D500009F3B65 /* Project object */; 27 | proxyType = 1; 28 | remoteGlobalIDString = EF2A4FE62014D501009F3B65; 29 | remoteInfo = SDTextLimit; 30 | }; 31 | EF2A500B2014D501009F3B65 /* PBXContainerItemProxy */ = { 32 | isa = PBXContainerItemProxy; 33 | containerPortal = EF2A4FDF2014D500009F3B65 /* Project object */; 34 | proxyType = 1; 35 | remoteGlobalIDString = EF2A4FE62014D501009F3B65; 36 | remoteInfo = SDTextLimit; 37 | }; 38 | /* End PBXContainerItemProxy section */ 39 | 40 | /* Begin PBXFileReference section */ 41 | EF2A4FE72014D501009F3B65 /* SDTextLimit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SDTextLimit.app; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | EF2A4FEA2014D501009F3B65 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 43 | EF2A4FEB2014D501009F3B65 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 44 | EF2A4FED2014D501009F3B65 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 45 | EF2A4FEE2014D501009F3B65 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 46 | EF2A4FF12014D501009F3B65 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 47 | EF2A4FF32014D501009F3B65 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 48 | EF2A4FF62014D501009F3B65 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 49 | EF2A4FF82014D501009F3B65 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50 | EF2A4FF92014D501009F3B65 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 51 | EF2A4FFF2014D501009F3B65 /* SDTextLimitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SDTextLimitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | EF2A50032014D501009F3B65 /* SDTextLimitTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDTextLimitTests.m; sourceTree = ""; }; 53 | EF2A50052014D501009F3B65 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 54 | EF2A500A2014D501009F3B65 /* SDTextLimitUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SDTextLimitUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 55 | EF2A500E2014D501009F3B65 /* SDTextLimitUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDTextLimitUITests.m; sourceTree = ""; }; 56 | EF2A50102014D501009F3B65 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 57 | EF2A501F2014DCBD009F3B65 /* SDTextLimitTool.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDTextLimitTool.h; sourceTree = ""; }; 58 | EF2A50202014DCBD009F3B65 /* SDTextLimitTool.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDTextLimitTool.m; sourceTree = ""; }; 59 | EF2A50222014DF38009F3B65 /* SDBaseTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDBaseTextView.h; sourceTree = ""; }; 60 | EF2A50232014DF38009F3B65 /* SDBaseTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDBaseTextView.m; sourceTree = ""; }; 61 | EF2A502B2014E1CA009F3B65 /* SDTextViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDTextViewController.h; sourceTree = ""; }; 62 | EF2A502C2014E1CA009F3B65 /* SDTextViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDTextViewController.m; sourceTree = ""; }; 63 | /* End PBXFileReference section */ 64 | 65 | /* Begin PBXFrameworksBuildPhase section */ 66 | EF2A4FE42014D501009F3B65 /* Frameworks */ = { 67 | isa = PBXFrameworksBuildPhase; 68 | buildActionMask = 2147483647; 69 | files = ( 70 | ); 71 | runOnlyForDeploymentPostprocessing = 0; 72 | }; 73 | EF2A4FFC2014D501009F3B65 /* Frameworks */ = { 74 | isa = PBXFrameworksBuildPhase; 75 | buildActionMask = 2147483647; 76 | files = ( 77 | ); 78 | runOnlyForDeploymentPostprocessing = 0; 79 | }; 80 | EF2A50072014D501009F3B65 /* Frameworks */ = { 81 | isa = PBXFrameworksBuildPhase; 82 | buildActionMask = 2147483647; 83 | files = ( 84 | ); 85 | runOnlyForDeploymentPostprocessing = 0; 86 | }; 87 | /* End PBXFrameworksBuildPhase section */ 88 | 89 | /* Begin PBXGroup section */ 90 | EF2A4FDE2014D500009F3B65 = { 91 | isa = PBXGroup; 92 | children = ( 93 | EF2A4FE92014D501009F3B65 /* SDTextLimit */, 94 | EF2A50022014D501009F3B65 /* SDTextLimitTests */, 95 | EF2A500D2014D501009F3B65 /* SDTextLimitUITests */, 96 | EF2A4FE82014D501009F3B65 /* Products */, 97 | ); 98 | sourceTree = ""; 99 | }; 100 | EF2A4FE82014D501009F3B65 /* Products */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | EF2A4FE72014D501009F3B65 /* SDTextLimit.app */, 104 | EF2A4FFF2014D501009F3B65 /* SDTextLimitTests.xctest */, 105 | EF2A500A2014D501009F3B65 /* SDTextLimitUITests.xctest */, 106 | ); 107 | name = Products; 108 | sourceTree = ""; 109 | }; 110 | EF2A4FE92014D501009F3B65 /* SDTextLimit */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | EF2A4FEA2014D501009F3B65 /* AppDelegate.h */, 114 | EF2A4FEB2014D501009F3B65 /* AppDelegate.m */, 115 | EF2A4FED2014D501009F3B65 /* ViewController.h */, 116 | EF2A4FEE2014D501009F3B65 /* ViewController.m */, 117 | EF2A502B2014E1CA009F3B65 /* SDTextViewController.h */, 118 | EF2A502C2014E1CA009F3B65 /* SDTextViewController.m */, 119 | EF2A501F2014DCBD009F3B65 /* SDTextLimitTool.h */, 120 | EF2A50202014DCBD009F3B65 /* SDTextLimitTool.m */, 121 | EF2A50222014DF38009F3B65 /* SDBaseTextView.h */, 122 | EF2A50232014DF38009F3B65 /* SDBaseTextView.m */, 123 | EF2A4FF02014D501009F3B65 /* Main.storyboard */, 124 | EF2A4FF32014D501009F3B65 /* Assets.xcassets */, 125 | EF2A4FF52014D501009F3B65 /* LaunchScreen.storyboard */, 126 | EF2A4FF82014D501009F3B65 /* Info.plist */, 127 | EF2A4FF92014D501009F3B65 /* main.m */, 128 | ); 129 | path = SDTextLimit; 130 | sourceTree = ""; 131 | }; 132 | EF2A50022014D501009F3B65 /* SDTextLimitTests */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | EF2A50032014D501009F3B65 /* SDTextLimitTests.m */, 136 | EF2A50052014D501009F3B65 /* Info.plist */, 137 | ); 138 | path = SDTextLimitTests; 139 | sourceTree = ""; 140 | }; 141 | EF2A500D2014D501009F3B65 /* SDTextLimitUITests */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | EF2A500E2014D501009F3B65 /* SDTextLimitUITests.m */, 145 | EF2A50102014D501009F3B65 /* Info.plist */, 146 | ); 147 | path = SDTextLimitUITests; 148 | sourceTree = ""; 149 | }; 150 | /* End PBXGroup section */ 151 | 152 | /* Begin PBXNativeTarget section */ 153 | EF2A4FE62014D501009F3B65 /* SDTextLimit */ = { 154 | isa = PBXNativeTarget; 155 | buildConfigurationList = EF2A50132014D501009F3B65 /* Build configuration list for PBXNativeTarget "SDTextLimit" */; 156 | buildPhases = ( 157 | EF2A4FE32014D501009F3B65 /* Sources */, 158 | EF2A4FE42014D501009F3B65 /* Frameworks */, 159 | EF2A4FE52014D501009F3B65 /* Resources */, 160 | ); 161 | buildRules = ( 162 | ); 163 | dependencies = ( 164 | ); 165 | name = SDTextLimit; 166 | productName = SDTextLimit; 167 | productReference = EF2A4FE72014D501009F3B65 /* SDTextLimit.app */; 168 | productType = "com.apple.product-type.application"; 169 | }; 170 | EF2A4FFE2014D501009F3B65 /* SDTextLimitTests */ = { 171 | isa = PBXNativeTarget; 172 | buildConfigurationList = EF2A50162014D501009F3B65 /* Build configuration list for PBXNativeTarget "SDTextLimitTests" */; 173 | buildPhases = ( 174 | EF2A4FFB2014D501009F3B65 /* Sources */, 175 | EF2A4FFC2014D501009F3B65 /* Frameworks */, 176 | EF2A4FFD2014D501009F3B65 /* Resources */, 177 | ); 178 | buildRules = ( 179 | ); 180 | dependencies = ( 181 | EF2A50012014D501009F3B65 /* PBXTargetDependency */, 182 | ); 183 | name = SDTextLimitTests; 184 | productName = SDTextLimitTests; 185 | productReference = EF2A4FFF2014D501009F3B65 /* SDTextLimitTests.xctest */; 186 | productType = "com.apple.product-type.bundle.unit-test"; 187 | }; 188 | EF2A50092014D501009F3B65 /* SDTextLimitUITests */ = { 189 | isa = PBXNativeTarget; 190 | buildConfigurationList = EF2A50192014D501009F3B65 /* Build configuration list for PBXNativeTarget "SDTextLimitUITests" */; 191 | buildPhases = ( 192 | EF2A50062014D501009F3B65 /* Sources */, 193 | EF2A50072014D501009F3B65 /* Frameworks */, 194 | EF2A50082014D501009F3B65 /* Resources */, 195 | ); 196 | buildRules = ( 197 | ); 198 | dependencies = ( 199 | EF2A500C2014D501009F3B65 /* PBXTargetDependency */, 200 | ); 201 | name = SDTextLimitUITests; 202 | productName = SDTextLimitUITests; 203 | productReference = EF2A500A2014D501009F3B65 /* SDTextLimitUITests.xctest */; 204 | productType = "com.apple.product-type.bundle.ui-testing"; 205 | }; 206 | /* End PBXNativeTarget section */ 207 | 208 | /* Begin PBXProject section */ 209 | EF2A4FDF2014D500009F3B65 /* Project object */ = { 210 | isa = PBXProject; 211 | attributes = { 212 | LastUpgradeCheck = 0910; 213 | ORGANIZATIONNAME = slowdony; 214 | TargetAttributes = { 215 | EF2A4FE62014D501009F3B65 = { 216 | CreatedOnToolsVersion = 9.1; 217 | ProvisioningStyle = Automatic; 218 | }; 219 | EF2A4FFE2014D501009F3B65 = { 220 | CreatedOnToolsVersion = 9.1; 221 | ProvisioningStyle = Automatic; 222 | TestTargetID = EF2A4FE62014D501009F3B65; 223 | }; 224 | EF2A50092014D501009F3B65 = { 225 | CreatedOnToolsVersion = 9.1; 226 | ProvisioningStyle = Automatic; 227 | TestTargetID = EF2A4FE62014D501009F3B65; 228 | }; 229 | }; 230 | }; 231 | buildConfigurationList = EF2A4FE22014D500009F3B65 /* Build configuration list for PBXProject "SDTextLimit" */; 232 | compatibilityVersion = "Xcode 8.0"; 233 | developmentRegion = en; 234 | hasScannedForEncodings = 0; 235 | knownRegions = ( 236 | en, 237 | Base, 238 | ); 239 | mainGroup = EF2A4FDE2014D500009F3B65; 240 | productRefGroup = EF2A4FE82014D501009F3B65 /* Products */; 241 | projectDirPath = ""; 242 | projectRoot = ""; 243 | targets = ( 244 | EF2A4FE62014D501009F3B65 /* SDTextLimit */, 245 | EF2A4FFE2014D501009F3B65 /* SDTextLimitTests */, 246 | EF2A50092014D501009F3B65 /* SDTextLimitUITests */, 247 | ); 248 | }; 249 | /* End PBXProject section */ 250 | 251 | /* Begin PBXResourcesBuildPhase section */ 252 | EF2A4FE52014D501009F3B65 /* Resources */ = { 253 | isa = PBXResourcesBuildPhase; 254 | buildActionMask = 2147483647; 255 | files = ( 256 | EF2A4FF72014D501009F3B65 /* LaunchScreen.storyboard in Resources */, 257 | EF2A4FF42014D501009F3B65 /* Assets.xcassets in Resources */, 258 | EF2A4FF22014D501009F3B65 /* Main.storyboard in Resources */, 259 | ); 260 | runOnlyForDeploymentPostprocessing = 0; 261 | }; 262 | EF2A4FFD2014D501009F3B65 /* Resources */ = { 263 | isa = PBXResourcesBuildPhase; 264 | buildActionMask = 2147483647; 265 | files = ( 266 | ); 267 | runOnlyForDeploymentPostprocessing = 0; 268 | }; 269 | EF2A50082014D501009F3B65 /* Resources */ = { 270 | isa = PBXResourcesBuildPhase; 271 | buildActionMask = 2147483647; 272 | files = ( 273 | ); 274 | runOnlyForDeploymentPostprocessing = 0; 275 | }; 276 | /* End PBXResourcesBuildPhase section */ 277 | 278 | /* Begin PBXSourcesBuildPhase section */ 279 | EF2A4FE32014D501009F3B65 /* Sources */ = { 280 | isa = PBXSourcesBuildPhase; 281 | buildActionMask = 2147483647; 282 | files = ( 283 | EF2A4FEF2014D501009F3B65 /* ViewController.m in Sources */, 284 | EF2A4FFA2014D501009F3B65 /* main.m in Sources */, 285 | EF2A4FEC2014D501009F3B65 /* AppDelegate.m in Sources */, 286 | EF2A50242014DF38009F3B65 /* SDBaseTextView.m in Sources */, 287 | EF2A50212014DCBD009F3B65 /* SDTextLimitTool.m in Sources */, 288 | EF2A502D2014E1CA009F3B65 /* SDTextViewController.m in Sources */, 289 | ); 290 | runOnlyForDeploymentPostprocessing = 0; 291 | }; 292 | EF2A4FFB2014D501009F3B65 /* Sources */ = { 293 | isa = PBXSourcesBuildPhase; 294 | buildActionMask = 2147483647; 295 | files = ( 296 | EF2A50042014D501009F3B65 /* SDTextLimitTests.m in Sources */, 297 | ); 298 | runOnlyForDeploymentPostprocessing = 0; 299 | }; 300 | EF2A50062014D501009F3B65 /* Sources */ = { 301 | isa = PBXSourcesBuildPhase; 302 | buildActionMask = 2147483647; 303 | files = ( 304 | EF2A500F2014D501009F3B65 /* SDTextLimitUITests.m in Sources */, 305 | ); 306 | runOnlyForDeploymentPostprocessing = 0; 307 | }; 308 | /* End PBXSourcesBuildPhase section */ 309 | 310 | /* Begin PBXTargetDependency section */ 311 | EF2A50012014D501009F3B65 /* PBXTargetDependency */ = { 312 | isa = PBXTargetDependency; 313 | target = EF2A4FE62014D501009F3B65 /* SDTextLimit */; 314 | targetProxy = EF2A50002014D501009F3B65 /* PBXContainerItemProxy */; 315 | }; 316 | EF2A500C2014D501009F3B65 /* PBXTargetDependency */ = { 317 | isa = PBXTargetDependency; 318 | target = EF2A4FE62014D501009F3B65 /* SDTextLimit */; 319 | targetProxy = EF2A500B2014D501009F3B65 /* PBXContainerItemProxy */; 320 | }; 321 | /* End PBXTargetDependency section */ 322 | 323 | /* Begin PBXVariantGroup section */ 324 | EF2A4FF02014D501009F3B65 /* Main.storyboard */ = { 325 | isa = PBXVariantGroup; 326 | children = ( 327 | EF2A4FF12014D501009F3B65 /* Base */, 328 | ); 329 | name = Main.storyboard; 330 | sourceTree = ""; 331 | }; 332 | EF2A4FF52014D501009F3B65 /* LaunchScreen.storyboard */ = { 333 | isa = PBXVariantGroup; 334 | children = ( 335 | EF2A4FF62014D501009F3B65 /* Base */, 336 | ); 337 | name = LaunchScreen.storyboard; 338 | sourceTree = ""; 339 | }; 340 | /* End PBXVariantGroup section */ 341 | 342 | /* Begin XCBuildConfiguration section */ 343 | EF2A50112014D501009F3B65 /* Debug */ = { 344 | isa = XCBuildConfiguration; 345 | buildSettings = { 346 | ALWAYS_SEARCH_USER_PATHS = NO; 347 | CLANG_ANALYZER_NONNULL = YES; 348 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 349 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 350 | CLANG_CXX_LIBRARY = "libc++"; 351 | CLANG_ENABLE_MODULES = YES; 352 | CLANG_ENABLE_OBJC_ARC = YES; 353 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 354 | CLANG_WARN_BOOL_CONVERSION = YES; 355 | CLANG_WARN_COMMA = YES; 356 | CLANG_WARN_CONSTANT_CONVERSION = YES; 357 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 358 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 359 | CLANG_WARN_EMPTY_BODY = YES; 360 | CLANG_WARN_ENUM_CONVERSION = YES; 361 | CLANG_WARN_INFINITE_RECURSION = YES; 362 | CLANG_WARN_INT_CONVERSION = YES; 363 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 364 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 365 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 366 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 367 | CLANG_WARN_STRICT_PROTOTYPES = YES; 368 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 369 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 370 | CLANG_WARN_UNREACHABLE_CODE = YES; 371 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 372 | CODE_SIGN_IDENTITY = "iPhone Developer"; 373 | COPY_PHASE_STRIP = NO; 374 | DEBUG_INFORMATION_FORMAT = dwarf; 375 | ENABLE_STRICT_OBJC_MSGSEND = YES; 376 | ENABLE_TESTABILITY = YES; 377 | GCC_C_LANGUAGE_STANDARD = gnu11; 378 | GCC_DYNAMIC_NO_PIC = NO; 379 | GCC_NO_COMMON_BLOCKS = YES; 380 | GCC_OPTIMIZATION_LEVEL = 0; 381 | GCC_PREPROCESSOR_DEFINITIONS = ( 382 | "DEBUG=1", 383 | "$(inherited)", 384 | ); 385 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 386 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 387 | GCC_WARN_UNDECLARED_SELECTOR = YES; 388 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 389 | GCC_WARN_UNUSED_FUNCTION = YES; 390 | GCC_WARN_UNUSED_VARIABLE = YES; 391 | IPHONEOS_DEPLOYMENT_TARGET = 11.1; 392 | MTL_ENABLE_DEBUG_INFO = YES; 393 | ONLY_ACTIVE_ARCH = YES; 394 | SDKROOT = iphoneos; 395 | }; 396 | name = Debug; 397 | }; 398 | EF2A50122014D501009F3B65 /* Release */ = { 399 | isa = XCBuildConfiguration; 400 | buildSettings = { 401 | ALWAYS_SEARCH_USER_PATHS = NO; 402 | CLANG_ANALYZER_NONNULL = YES; 403 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 404 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 405 | CLANG_CXX_LIBRARY = "libc++"; 406 | CLANG_ENABLE_MODULES = YES; 407 | CLANG_ENABLE_OBJC_ARC = YES; 408 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 409 | CLANG_WARN_BOOL_CONVERSION = YES; 410 | CLANG_WARN_COMMA = YES; 411 | CLANG_WARN_CONSTANT_CONVERSION = YES; 412 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 413 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 414 | CLANG_WARN_EMPTY_BODY = YES; 415 | CLANG_WARN_ENUM_CONVERSION = YES; 416 | CLANG_WARN_INFINITE_RECURSION = YES; 417 | CLANG_WARN_INT_CONVERSION = YES; 418 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 419 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 420 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 421 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 422 | CLANG_WARN_STRICT_PROTOTYPES = YES; 423 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 424 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 425 | CLANG_WARN_UNREACHABLE_CODE = YES; 426 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 427 | CODE_SIGN_IDENTITY = "iPhone Developer"; 428 | COPY_PHASE_STRIP = NO; 429 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 430 | ENABLE_NS_ASSERTIONS = NO; 431 | ENABLE_STRICT_OBJC_MSGSEND = YES; 432 | GCC_C_LANGUAGE_STANDARD = gnu11; 433 | GCC_NO_COMMON_BLOCKS = YES; 434 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 435 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 436 | GCC_WARN_UNDECLARED_SELECTOR = YES; 437 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 438 | GCC_WARN_UNUSED_FUNCTION = YES; 439 | GCC_WARN_UNUSED_VARIABLE = YES; 440 | IPHONEOS_DEPLOYMENT_TARGET = 11.1; 441 | MTL_ENABLE_DEBUG_INFO = NO; 442 | SDKROOT = iphoneos; 443 | VALIDATE_PRODUCT = YES; 444 | }; 445 | name = Release; 446 | }; 447 | EF2A50142014D501009F3B65 /* Debug */ = { 448 | isa = XCBuildConfiguration; 449 | buildSettings = { 450 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 451 | CODE_SIGN_STYLE = Automatic; 452 | INFOPLIST_FILE = SDTextLimit/Info.plist; 453 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 454 | PRODUCT_BUNDLE_IDENTIFIER = slowdony.SDTextLimit; 455 | PRODUCT_NAME = "$(TARGET_NAME)"; 456 | TARGETED_DEVICE_FAMILY = "1,2"; 457 | }; 458 | name = Debug; 459 | }; 460 | EF2A50152014D501009F3B65 /* Release */ = { 461 | isa = XCBuildConfiguration; 462 | buildSettings = { 463 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 464 | CODE_SIGN_STYLE = Automatic; 465 | INFOPLIST_FILE = SDTextLimit/Info.plist; 466 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 467 | PRODUCT_BUNDLE_IDENTIFIER = slowdony.SDTextLimit; 468 | PRODUCT_NAME = "$(TARGET_NAME)"; 469 | TARGETED_DEVICE_FAMILY = "1,2"; 470 | }; 471 | name = Release; 472 | }; 473 | EF2A50172014D501009F3B65 /* Debug */ = { 474 | isa = XCBuildConfiguration; 475 | buildSettings = { 476 | BUNDLE_LOADER = "$(TEST_HOST)"; 477 | CODE_SIGN_STYLE = Automatic; 478 | INFOPLIST_FILE = SDTextLimitTests/Info.plist; 479 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 480 | PRODUCT_BUNDLE_IDENTIFIER = slowdony.SDTextLimitTests; 481 | PRODUCT_NAME = "$(TARGET_NAME)"; 482 | TARGETED_DEVICE_FAMILY = "1,2"; 483 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SDTextLimit.app/SDTextLimit"; 484 | }; 485 | name = Debug; 486 | }; 487 | EF2A50182014D501009F3B65 /* Release */ = { 488 | isa = XCBuildConfiguration; 489 | buildSettings = { 490 | BUNDLE_LOADER = "$(TEST_HOST)"; 491 | CODE_SIGN_STYLE = Automatic; 492 | INFOPLIST_FILE = SDTextLimitTests/Info.plist; 493 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 494 | PRODUCT_BUNDLE_IDENTIFIER = slowdony.SDTextLimitTests; 495 | PRODUCT_NAME = "$(TARGET_NAME)"; 496 | TARGETED_DEVICE_FAMILY = "1,2"; 497 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SDTextLimit.app/SDTextLimit"; 498 | }; 499 | name = Release; 500 | }; 501 | EF2A501A2014D501009F3B65 /* Debug */ = { 502 | isa = XCBuildConfiguration; 503 | buildSettings = { 504 | CODE_SIGN_STYLE = Automatic; 505 | INFOPLIST_FILE = SDTextLimitUITests/Info.plist; 506 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 507 | PRODUCT_BUNDLE_IDENTIFIER = slowdony.SDTextLimitUITests; 508 | PRODUCT_NAME = "$(TARGET_NAME)"; 509 | TARGETED_DEVICE_FAMILY = "1,2"; 510 | TEST_TARGET_NAME = SDTextLimit; 511 | }; 512 | name = Debug; 513 | }; 514 | EF2A501B2014D501009F3B65 /* Release */ = { 515 | isa = XCBuildConfiguration; 516 | buildSettings = { 517 | CODE_SIGN_STYLE = Automatic; 518 | INFOPLIST_FILE = SDTextLimitUITests/Info.plist; 519 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 520 | PRODUCT_BUNDLE_IDENTIFIER = slowdony.SDTextLimitUITests; 521 | PRODUCT_NAME = "$(TARGET_NAME)"; 522 | TARGETED_DEVICE_FAMILY = "1,2"; 523 | TEST_TARGET_NAME = SDTextLimit; 524 | }; 525 | name = Release; 526 | }; 527 | /* End XCBuildConfiguration section */ 528 | 529 | /* Begin XCConfigurationList section */ 530 | EF2A4FE22014D500009F3B65 /* Build configuration list for PBXProject "SDTextLimit" */ = { 531 | isa = XCConfigurationList; 532 | buildConfigurations = ( 533 | EF2A50112014D501009F3B65 /* Debug */, 534 | EF2A50122014D501009F3B65 /* Release */, 535 | ); 536 | defaultConfigurationIsVisible = 0; 537 | defaultConfigurationName = Release; 538 | }; 539 | EF2A50132014D501009F3B65 /* Build configuration list for PBXNativeTarget "SDTextLimit" */ = { 540 | isa = XCConfigurationList; 541 | buildConfigurations = ( 542 | EF2A50142014D501009F3B65 /* Debug */, 543 | EF2A50152014D501009F3B65 /* Release */, 544 | ); 545 | defaultConfigurationIsVisible = 0; 546 | defaultConfigurationName = Release; 547 | }; 548 | EF2A50162014D501009F3B65 /* Build configuration list for PBXNativeTarget "SDTextLimitTests" */ = { 549 | isa = XCConfigurationList; 550 | buildConfigurations = ( 551 | EF2A50172014D501009F3B65 /* Debug */, 552 | EF2A50182014D501009F3B65 /* Release */, 553 | ); 554 | defaultConfigurationIsVisible = 0; 555 | defaultConfigurationName = Release; 556 | }; 557 | EF2A50192014D501009F3B65 /* Build configuration list for PBXNativeTarget "SDTextLimitUITests" */ = { 558 | isa = XCConfigurationList; 559 | buildConfigurations = ( 560 | EF2A501A2014D501009F3B65 /* Debug */, 561 | EF2A501B2014D501009F3B65 /* Release */, 562 | ); 563 | defaultConfigurationIsVisible = 0; 564 | defaultConfigurationName = Release; 565 | }; 566 | /* End XCConfigurationList section */ 567 | }; 568 | rootObject = EF2A4FDF2014D500009F3B65 /* Project object */; 569 | } 570 | -------------------------------------------------------------------------------- /SDTextLimit.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SDTextLimit.xcodeproj/project.xcworkspace/xcuserdata/slowdony.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlowDony/SDTextLimit/8f9fcf2c3460c6058c0d4a264b3fce499cc4ebb4/SDTextLimit.xcodeproj/project.xcworkspace/xcuserdata/slowdony.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /SDTextLimit.xcodeproj/xcuserdata/slowdony.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SDTextLimit.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /SDTextLimit/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // SDTextLimit 4 | // 5 | // Created by slowdony on 2018/1/21. 6 | // Copyright © 2018年 slowdony. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /SDTextLimit/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // SDTextLimit 4 | // 5 | // Created by slowdony on 2018/1/21. 6 | // Copyright © 2018年 slowdony. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "ViewController.h" 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | 21 | UINavigationController *nav = [[UINavigationController alloc]initWithRootViewController:[[ViewController alloc]init]]; 22 | self.window.rootViewController = nav; 23 | return YES; 24 | } 25 | 26 | 27 | - (void)applicationWillResignActive:(UIApplication *)application { 28 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 29 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 30 | } 31 | 32 | 33 | - (void)applicationDidEnterBackground:(UIApplication *)application { 34 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 35 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 36 | } 37 | 38 | 39 | - (void)applicationWillEnterForeground:(UIApplication *)application { 40 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 41 | } 42 | 43 | 44 | - (void)applicationDidBecomeActive:(UIApplication *)application { 45 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 46 | } 47 | 48 | 49 | - (void)applicationWillTerminate:(UIApplication *)application { 50 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 51 | } 52 | 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /SDTextLimit/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /SDTextLimit/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SDTextLimit/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /SDTextLimit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /SDTextLimit/SDBaseTextView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SDBaseTextView.h 3 | // miaohu 4 | // 5 | // Created by Megatron Joker on 2017/4/20. 6 | // Copyright © 2017年 SlowDony. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SDBaseTextView : UITextView 12 | 13 | /** 14 | * 占位文字 15 | */ 16 | @property (nonatomic, copy) NSString *placeholder; 17 | /** 18 | * 占位文字颜色 19 | */ 20 | @property (nonatomic, strong) UIColor *placeholderColor; 21 | @end 22 | -------------------------------------------------------------------------------- /SDTextLimit/SDBaseTextView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SDBaseTextView.m 3 | // miaohu 4 | // 5 | // Created by Megatron Joker on 2017/4/20. 6 | // Copyright © 2017年 SlowDony. All rights reserved. 7 | // 8 | 9 | #import "SDBaseTextView.h" 10 | 11 | @implementation SDBaseTextView 12 | 13 | /* 14 | // Only override drawRect: if you perform custom drawing. 15 | // An empty implementation adversely affects performance during animation. 16 | - (void)drawRect:(CGRect)rect { 17 | // Drawing code 18 | } 19 | */ 20 | - (instancetype)initWithFrame:(CGRect)frame 21 | { 22 | self = [super initWithFrame:frame]; 23 | if (self) { 24 | // 当UITextView的文字发生改变时,UITextView自己会发出一个UITextViewTextDidChangeNotification通知 25 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChange) name:UITextViewTextDidChangeNotification object:self]; 26 | self.tintColor = [UIColor darkTextColor]; 27 | } 28 | return self; 29 | } 30 | 31 | /** 32 | * 监听文字改变 33 | */ 34 | -(void)textDidChange { 35 | NSLog(@"ha"); 36 | //重绘 37 | [self setNeedsDisplay]; 38 | 39 | } 40 | 41 | -(void)setPlaceholder:(NSString *)placeholder { 42 | _placeholder = placeholder; 43 | // setNeedsDisplay会在下一个消息循环时刻,调用drawRect: 44 | [self setNeedsDisplay]; 45 | } 46 | 47 | -(void)setText:(NSString *)text { 48 | [super setText:text]; 49 | // setNeedsDisplay会在下一个消息循环时刻,调用drawRect: 50 | [self setNeedsDisplay]; 51 | } 52 | 53 | -(void)setFont:(UIFont *)font { 54 | [super setFont:font]; 55 | // setNeedsDisplay会在下一个消息循环时刻,调用drawRect: 56 | [self setNeedsDisplay]; 57 | } 58 | 59 | - (void)drawRect:(CGRect)rect { 60 | 61 | // 如果有输入文字,就直接返回,不画占位文字 62 | if (self.hasText) return; 63 | //设置文字属性 64 | NSMutableDictionary *attributes = [NSMutableDictionary dictionary]; 65 | attributes[NSFontAttributeName] = self.font; 66 | attributes[NSForegroundColorAttributeName] = self.placeholderColor ? self.placeholderColor : [UIColor grayColor]; 67 | //画文字 68 | CGFloat x = 5; 69 | CGFloat width = rect.size.width -2 * x; 70 | CGFloat y = 8; 71 | CGFloat height = rect.size.height - 2 * y; 72 | CGRect placeholderRect = CGRectMake(x, y, width, height); 73 | [self.placeholder drawInRect:placeholderRect withAttributes:attributes]; 74 | } 75 | 76 | -(void)dealloc { 77 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 78 | } 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /SDTextLimit/SDTextLimitTool.h: -------------------------------------------------------------------------------- 1 | // 2 | // SDTextLimitTool.h 3 | // SDTextLimit 4 | // 5 | // Created by slowdony on 2018/1/21. 6 | // Copyright © 2018年 slowdony. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | @interface SDTextLimitTool : NSObject 12 | 13 | 14 | /** 15 | 判断是否有表情 16 | */ 17 | + (BOOL)isContainsEmoji:(NSString *)string; 18 | 19 | /** 20 | 删除emoji表情 21 | */ 22 | + (NSString *)disableEmoji:(NSString *)text; 23 | 24 | /** 25 | 判断是否有特殊字符 26 | */ 27 | + (BOOL)isContainsSpecialCharacters:(NSString *)string; 28 | 29 | /** 30 | 汉字,数字,英文,括号,下划线,横杠,空格(只能输入这些) 31 | */ 32 | +(NSString *)filterCharactors:(NSString *)string; 33 | 34 | /** 35 | 根据正在过滤特殊字符 36 | */ 37 | + (NSString *)filterCharactor:(NSString *)string withRegex:(NSString *)regexStr; 38 | 39 | /** 40 | 除去特殊字符并限制字数的textFiled 41 | */ 42 | + (void)restrictionInputTextFieldMaskSpecialCharacter:(UITextField *)textField maxNumber:(NSInteger)maxNumber; 43 | 44 | /** 45 | textFiled限制字数 46 | */ 47 | + (void)restrictionInputTextField:(UITextField *)textField maxNumber:(NSInteger)maxNumber; 48 | 49 | /** 50 | 除去特殊字符并限制字数的TextView 51 | */ 52 | + (void)restrictionInputTextViewMaskSpecialCharacter:(UITextView *)textView maxNumber:(NSInteger)maxNumber; 53 | 54 | /** 55 | textView限制字数 56 | */ 57 | + (void)restrictionInputTextView:(UITextView *)textView maxNumber:(NSInteger)maxNumber; 58 | 59 | /** 60 | 防止原生emoji表情被截断 61 | */ 62 | + (NSString *)subStringWith:(NSString *)string index:(NSInteger)index; 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /SDTextLimit/SDTextLimitTool.m: -------------------------------------------------------------------------------- 1 | // 2 | // SDTextLimitTool.m 3 | // SDTextLimit 4 | // 5 | // Created by slowdony on 2018/1/21. 6 | // Copyright © 2018年 slowdony. All rights reserved. 7 | // 8 | 9 | #import "SDTextLimitTool.h" 10 | 11 | @implementation SDTextLimitTool 12 | 13 | /** 14 | 判断NSString中是否有表情 15 | */ 16 | + (BOOL)isContainsEmoji:(NSString *)string { 17 | __block BOOL isEomji = NO; 18 | [string enumerateSubstringsInRange:NSMakeRange(0, [string length]) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { 19 | const unichar hs = [substring characterAtIndex:0]; 20 | // surrogate pair 21 | if (0xd800 <= hs && hs <= 0xdbff) { 22 | if (substring.length > 1) { 23 | const unichar ls = [substring characterAtIndex:1]; 24 | const int uc = ((hs - 0xd800) * 0x400) + (ls - 0xdc00) + 0x10000; 25 | if (0x1d000 <= uc && uc <= 0x1f77f) { 26 | isEomji = YES; 27 | } 28 | } 29 | } else { 30 | // non surrogate 31 | if (0x2100 <= hs && hs <= 0x27ff && hs != 0x263b) { 32 | if (!(9312 <= hs && hs <= 9327)) { // 9312代表① 表示①至⑯ 33 | isEomji = YES; 34 | } 35 | } else if (0x2B05 <= hs && hs <= 0x2b07) { 36 | isEomji = YES; 37 | } else if (0x2934 <= hs && hs <= 0x2935) { 38 | isEomji = YES; 39 | } else if (0x3297 <= hs && hs <= 0x3299) { 40 | isEomji = YES; 41 | } else if (hs == 0xa9 || hs == 0xae || hs == 0x303d || hs == 0x3030 || hs == 0x2b55 || hs == 0x2b1c || hs == 0x2b1b || hs == 0x2b50|| hs == 0x231a ) { 42 | isEomji = YES; 43 | } 44 | if (!isEomji && substring.length > 1) { 45 | const unichar ls = [substring characterAtIndex:1]; 46 | if (ls == 0x20e3) { 47 | isEomji = YES; 48 | } 49 | } 50 | } 51 | }]; 52 | return isEomji; 53 | } 54 | 55 | /** 56 | 删除emoji表情 57 | */ 58 | + (NSString *)disableEmoji:(NSString *)text{ 59 | NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[^\\u0020-\\u007E\\u00A0-\\u00BE\\u2E80-\\uA4CF\\uF900-\\uFAFF\\uFE30-\\uFE4F\\uFF00-\\uFFEF\\u0080-\\u009F\\u2000-\\u201f\r\n]"options:NSRegularExpressionCaseInsensitive error:nil]; 60 | NSString *modifiedString = [regex stringByReplacingMatchesInString:text 61 | options:0 62 | range:NSMakeRange(0, [text length]) 63 | withTemplate:@""]; 64 | return modifiedString; 65 | } 66 | 67 | /** 68 | 只能输入汉字,数字,英文,括号,下划线,横杠,空格 69 | */ 70 | +(NSString *)filterCharactors:(NSString *)string{ 71 | 72 | NSString *regular = @"[^a-zA-Z0-9()_\u4E00-\u9FA5\\s-]"; // 73 | NSString *str = [[self class] filterCharactor:string withRegex:[NSString stringWithFormat:@"%@",regular]]; 74 | 75 | return str; 76 | } 77 | 78 | /** 79 | 判断是否存在特殊字符 只能输入汉字,数字,英文,括号,下划线,横杠,空格 80 | */ 81 | + (BOOL)isContainsSpecialCharacters:(NSString *)searchText 82 | { 83 | // NSString *regex = @"[^a-zA-Z0-9()_\u4E00-\u9FA5\\s-]"; 84 | // NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex]; 85 | // BOOL isValid = [predicate evaluateWithObject:string]; 86 | // return isValid; 87 | 88 | NSError *error = NULL; 89 | NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[^a-zA-Z0-9()_\u4E00-\u9FA5\\s-]" options:NSRegularExpressionCaseInsensitive error:&error]; 90 | NSTextCheckingResult *result = [regex firstMatchInString:searchText options:0 range:NSMakeRange(0, [searchText length])]; 91 | if (result) { 92 | return YES; 93 | }else { 94 | return NO; 95 | } 96 | 97 | } 98 | 99 | 100 | /** 101 | 根据正则过滤特殊字符 102 | */ 103 | + (NSString *)filterCharactor:(NSString *)string withRegex:(NSString *)regexStr{ 104 | NSString *searchText = string; 105 | NSError *error = NULL; 106 | NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regexStr options:NSRegularExpressionCaseInsensitive error:&error]; 107 | NSString *result = [regex stringByReplacingMatchesInString:searchText options:NSMatchingReportCompletion range:NSMakeRange(0, searchText.length) withTemplate:@""]; 108 | return result; 109 | } 110 | 111 | 112 | /** 113 | 除去特殊字符并限制字数的textFiled 114 | */ 115 | + (void)restrictionInputTextFieldMaskSpecialCharacter:(UITextField *)textField maxNumber:(NSInteger)maxNumber{ 116 | 117 | if ([[self class]isContainsEmoji:textField.text]){ 118 | textField.text = [[self class]disableEmoji:textField.text]; 119 | return; 120 | } 121 | if ([[self class]isContainsSpecialCharacters:textField.text]){ 122 | textField.text = [[self class]filterCharactors:textField.text]; 123 | return; 124 | } 125 | [[self class]restrictionInputTextField:textField maxNumber:maxNumber]; 126 | } 127 | /** 128 | 除去特殊字符并限制字数的TextView 129 | */ 130 | + (void)restrictionInputTextViewMaskSpecialCharacter:(UITextView *)textView maxNumber:(NSInteger)maxNumber{ 131 | 132 | if ([[self class]isContainsEmoji:textView.text]){ 133 | textView.text = [[self class]disableEmoji:textView.text]; 134 | return; 135 | } 136 | if ([[self class]isContainsSpecialCharacters:textView.text]){ 137 | textView.text = [[self class]filterCharactors:textView.text]; 138 | return; 139 | } 140 | [[self class]restrictionInputTextView:textView maxNumber:maxNumber]; 141 | } 142 | 143 | /** 144 | textFiled限制字数 145 | */ 146 | + (void)restrictionInputTextField:(UITextField *)textField maxNumber:(NSInteger)maxNumber{ 147 | 148 | NSString *toBeString = textField.text; 149 | NSString *lang = textField.textInputMode.primaryLanguage; // 键盘输入模式 150 | if([lang isEqualToString:@"zh-Hans"]) { //简体中文输入,包括简体拼音,健体五笔,简体手写 151 | UITextRange *selectedRange = [textField markedTextRange]; 152 | //获取高亮部分 153 | UITextPosition *position = [textField positionFromPosition:selectedRange.start offset:0]; 154 | //没有高亮选择的字,则对已输入的文字进行字数统计和限制 155 | if(!position) { 156 | 157 | if(toBeString.length > maxNumber) { 158 | textField.text = [toBeString substringToIndex:maxNumber]; 159 | } 160 | } else{ //有高亮选择的字符串,则暂不对文字进行统计和限制 161 | 162 | } 163 | } 164 | else{ //中文输入法以外的直接对其统计限制即可,不考虑其他语种情况 165 | 166 | if(toBeString.length > maxNumber) { 167 | //防止表情被截段 168 | textField.text = [[self class] subStringWith:toBeString index:maxNumber]; 169 | } 170 | } 171 | 172 | } 173 | 174 | /** 175 | textView限制字数 176 | */ 177 | + (void)restrictionInputTextView:(UITextView *)textView maxNumber:(NSInteger)maxNumber{ 178 | 179 | NSString *toBeString = textView.text; 180 | NSString *lang = textView.textInputMode.primaryLanguage; // 键盘输入模式 181 | if([lang isEqualToString:@"zh-Hans"]) { //简体中文输入,包括简体拼音,健体五笔,简体手写 182 | UITextRange *selectedRange = [textView markedTextRange]; 183 | //获取高亮部分 184 | UITextPosition *position = [textView positionFromPosition:selectedRange.start offset:0]; 185 | //没有高亮选择的字,则对已输入的文字进行字数统计和限制 186 | if(!position) { 187 | if(toBeString.length > maxNumber) { 188 | textView.text = [toBeString substringToIndex:maxNumber]; 189 | } 190 | } else{ //有高亮选择的字符串,则暂不对文字进行统计和限制 191 | } 192 | } else{ //中文输入法以外的直接对其统计限制即可,不考虑其他语种情况 193 | if(toBeString.length > maxNumber) { 194 | 195 | //防止表情被截段 196 | textView.text = [[self class] subStringWith:toBeString index:maxNumber]; 197 | } 198 | } 199 | } 200 | 201 | /** 202 | 防止原生emoji表情被截断 203 | */ 204 | + (NSString *)subStringWith:(NSString *)string index:(NSInteger)index{ 205 | 206 | NSString *result = string; 207 | if (result.length > index) { 208 | NSRange rangeIndex = [result rangeOfComposedCharacterSequenceAtIndex:index]; 209 | result = [result substringToIndex:(rangeIndex.location)]; 210 | } 211 | 212 | return result; 213 | } 214 | @end 215 | -------------------------------------------------------------------------------- /SDTextLimit/SDTextViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SDTextViewController.h 3 | // SDTextLimit 4 | // 5 | // Created by slowdony on 2018/1/21. 6 | // Copyright © 2018年 slowdony. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM(NSInteger ,TextViewVCType) { 12 | TextViewVCTypeTextFiledNum, //TextFiled限制字数 13 | TextViewVCTypeTextFiledNumCharacter, //TextFiled限制字数和特殊字符 14 | TextViewVCTypeTextViewNum, //TextView限制字数 15 | TextViewVCTypeTextViewNumCharacter //TextView限制字数和特殊字符 16 | }; 17 | 18 | @interface SDTextViewController : UIViewController 19 | @property (nonatomic,assign) TextViewVCType textViewVCType;// 20 | @property (nonatomic,copy) NSString *placeholder; 21 | @end 22 | -------------------------------------------------------------------------------- /SDTextLimit/SDTextViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SDTextViewController.m 3 | // SDTextLimit 4 | // 5 | // Created by slowdony on 2018/1/21. 6 | // Copyright © 2018年 slowdony. All rights reserved. 7 | // 8 | 9 | #import "SDTextViewController.h" 10 | #import "SDBaseTextView.h" 11 | #import "SDTextLimitTool.h" 12 | #define height [UIScreen mainScreen].bounds.size.height 13 | #define width [UIScreen mainScreen].bounds.size.width 14 | @interface SDTextViewController () 15 | 16 | 17 | 18 | /** 19 | textView 20 | */ 21 | @property (nonatomic,strong) SDBaseTextView * textView; 22 | /** 23 | textFiled 24 | */ 25 | @property (nonatomic,strong) UITextField *textFiled; 26 | 27 | 28 | @end 29 | 30 | @implementation SDTextViewController 31 | 32 | #pragma mark - lazy 33 | 34 | -(UITextField *)textFiled{ 35 | if (!_textFiled) { 36 | _textFiled = [[UITextField alloc] init]; 37 | _textFiled.frame = CGRectMake(20, (height-30)/2, width-40, 30); 38 | _textFiled.delegate = self; 39 | _textFiled.tag = 0; 40 | _textFiled.placeholder = self.placeholder; 41 | _textFiled.font = [UIFont systemFontOfSize:15]; 42 | _textFiled.textColor =[UIColor blackColor]; 43 | _textFiled.clearButtonMode = UITextFieldViewModeNever; 44 | _textFiled.keyboardType = UIKeyboardTypeDefault; 45 | _textFiled.autocorrectionType = UITextAutocorrectionTypeNo; 46 | _textFiled.borderStyle = UITextBorderStyleRoundedRect; 47 | [_textFiled addTarget:self action:@selector(textFiledChanged:) forControlEvents:UIControlEventEditingChanged]; 48 | } 49 | return _textFiled; 50 | } 51 | 52 | -(SDBaseTextView *)textView{ 53 | if (!_textView){ 54 | _textView =[[SDBaseTextView alloc]init]; 55 | _textView.frame =CGRectMake(20, (height-100)/2, width-40, 100); 56 | _textView.delegate = self; 57 | _textView.placeholder = self.placeholder; 58 | _textView.backgroundColor = [UIColor clearColor]; 59 | _textView.textColor = [UIColor blackColor]; 60 | _textView.textAlignment = NSTextAlignmentLeft; 61 | _textView.font = [UIFont systemFontOfSize:15]; 62 | _textView.layer.cornerRadius = 4; 63 | _textView.layer.borderColor = [UIColor colorWithWhite:0.8 alpha:0.8].CGColor; 64 | _textView.layer.borderWidth = 1; 65 | _textView.layer.masksToBounds = YES; 66 | } 67 | return _textView; 68 | } 69 | - (void)viewDidLoad { 70 | [super viewDidLoad]; 71 | self.title = @"SDTextViewController"; 72 | self.view.backgroundColor = [UIColor whiteColor]; 73 | if(self.textViewVCType==TextViewVCTypeTextFiledNum || self.textViewVCType == TextViewVCTypeTextFiledNumCharacter){ 74 | [self setupTextFiled]; 75 | } 76 | else { 77 | [self setupTextView]; 78 | } 79 | // Do any additional setup after loading the view. 80 | } 81 | 82 | -(void)setupTextFiled{ 83 | [self.view addSubview:self.textFiled]; 84 | } 85 | 86 | -(void)setupTextView{ 87 | [self.view addSubview:self.textView]; 88 | } 89 | 90 | - (void)textFiledChanged:(UITextField *)textFiled{ 91 | if (self.textViewVCType == TextViewVCTypeTextFiledNum){ 92 | 93 | [SDTextLimitTool restrictionInputTextField:textFiled maxNumber:15]; 94 | 95 | }else { 96 | [SDTextLimitTool restrictionInputTextFieldMaskSpecialCharacter:textFiled maxNumber:15]; 97 | } 98 | 99 | 100 | } 101 | 102 | - (void)textViewDidChange:(UITextView *)textView{ 103 | if (self.textViewVCType == TextViewVCTypeTextViewNum){ 104 | [SDTextLimitTool restrictionInputTextView:textView maxNumber:15]; 105 | }else { 106 | [SDTextLimitTool restrictionInputTextViewMaskSpecialCharacter:textView maxNumber:15]; 107 | } 108 | } 109 | 110 | 111 | - (void)didReceiveMemoryWarning { 112 | [super didReceiveMemoryWarning]; 113 | // Dispose of any resources that can be recreated. 114 | } 115 | 116 | /* 117 | #pragma mark - Navigation 118 | 119 | // In a storyboard-based application, you will often want to do a little preparation before navigation 120 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 121 | // Get the new view controller using [segue destinationViewController]. 122 | // Pass the selected object to the new view controller. 123 | } 124 | */ 125 | 126 | @end 127 | -------------------------------------------------------------------------------- /SDTextLimit/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // SDTextLimit 4 | // 5 | // Created by slowdony on 2018/1/21. 6 | // Copyright © 2018年 slowdony. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /SDTextLimit/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // SDTextLimit 4 | // 5 | // Created by slowdony on 2018/1/21. 6 | // Copyright © 2018年 slowdony. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "SDTextViewController.h" 11 | @interface ViewController () 12 | < 13 | UITableViewDelegate, 14 | UITableViewDataSource 15 | > 16 | /** 17 | 列表 18 | */ 19 | @property (nonatomic,strong) UITableView * tableView; 20 | 21 | /** 22 | 数据源 23 | */ 24 | @property (nonatomic,strong) NSMutableArray * dataArr; 25 | 26 | @end 27 | 28 | @implementation ViewController 29 | 30 | #pragma mark - lazy 31 | -(NSMutableArray *)dataArr{ 32 | if (!_dataArr){ 33 | NSArray *arr = @[ 34 | @"textField限制字数(15个)", 35 | @"textField限制字数(15个)和特殊字符", 36 | @"textView限制字数(15个)", 37 | @"textView限制字数(15个)和特殊字符", 38 | ]; 39 | 40 | _dataArr = [NSMutableArray arrayWithArray:arr]; 41 | } 42 | return _dataArr; 43 | } 44 | -(UITableView *)tableView{ 45 | if (!_tableView){ 46 | // 47 | _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height) style:UITableViewStylePlain]; 48 | _tableView.delegate = self; 49 | _tableView.dataSource = self; 50 | _tableView.showsVerticalScrollIndicator = NO; 51 | _tableView.backgroundColor = [UIColor clearColor]; 52 | 53 | } 54 | return _tableView; 55 | } 56 | - (void)viewDidLoad { 57 | [super viewDidLoad]; 58 | self.title = @"SDTextLimit"; 59 | self.view.backgroundColor = [UIColor whiteColor]; 60 | // Do any additional setup after loading the view, typically from a nib. 61 | [self.view addSubview:self.tableView]; 62 | } 63 | 64 | #pragma mark ----------UITabelViewDataSource---------- 65 | -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 66 | { 67 | return 1; 68 | } 69 | 70 | -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 71 | { 72 | return [self.dataArr count]; 73 | } 74 | 75 | -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 76 | { 77 | static NSString *cellId = @"cellID"; 78 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId]; 79 | if (cell == nil) { 80 | cell =[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId]; 81 | } 82 | //配置数据 83 | cell.textLabel.text = self.dataArr[indexPath.row]; 84 | 85 | return cell; 86 | } 87 | 88 | 89 | #pragma mark ----------UITabelViewDelegate---------- 90 | -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 91 | { 92 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 93 | SDTextViewController *textVC = [[SDTextViewController alloc]init]; 94 | textVC.placeholder = self.dataArr[indexPath.row]; 95 | textVC.textViewVCType = indexPath.row; 96 | [self.navigationController pushViewController:textVC animated:YES]; 97 | 98 | } 99 | 100 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 101 | { 102 | return 50; 103 | } 104 | 105 | 106 | - (void)didReceiveMemoryWarning { 107 | [super didReceiveMemoryWarning]; 108 | // Dispose of any resources that can be recreated. 109 | } 110 | 111 | 112 | @end 113 | -------------------------------------------------------------------------------- /SDTextLimit/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // SDTextLimit 4 | // 5 | // Created by slowdony on 2018/1/21. 6 | // Copyright © 2018年 slowdony. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /SDTextLimitTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /SDTextLimitTests/SDTextLimitTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // SDTextLimitTests.m 3 | // SDTextLimitTests 4 | // 5 | // Created by slowdony on 2018/1/21. 6 | // Copyright © 2018年 slowdony. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SDTextLimitTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation SDTextLimitTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /SDTextLimitUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /SDTextLimitUITests/SDTextLimitUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // SDTextLimitUITests.m 3 | // SDTextLimitUITests 4 | // 5 | // Created by slowdony on 2018/1/21. 6 | // Copyright © 2018年 slowdony. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SDTextLimitUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation SDTextLimitUITests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | 22 | // In UI tests it is usually best to stop immediately when a failure occurs. 23 | self.continueAfterFailure = NO; 24 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 25 | [[[XCUIApplication alloc] init] launch]; 26 | 27 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 28 | } 29 | 30 | - (void)tearDown { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | [super tearDown]; 33 | } 34 | 35 | - (void)testExample { 36 | // Use recording to get started writing UI tests. 37 | // Use XCTAssert and related functions to verify your tests produce the correct results. 38 | } 39 | 40 | @end 41 | --------------------------------------------------------------------------------