美文网首页iOS DeveloperiOS 开发 iOS Development
限制UITextField字数的正确姿势

限制UITextField字数的正确姿势

作者: KevinTing | 来源:发表于2016-09-17 19:28 被阅读1930次

    限制UITextField的字数应该是一个很常见的需求吧。前些时我们项目就有个,比方说用户名不能超过20个字符,实现也很简单,实现UITextFieldDelegate方法:

    - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
    {
        return textField.text.length + string.length < 21;
    }
    

    恩,这样一般也是没什么问题的,但是偏偏有不一般的情况。如果你的应用只用支持iPhone,那么你不用往下看了,上面的代码没什么问题,如果你要在iPad上面运行的话,那么这种情况下它会crash:
    1、iOS 9以上的iPad,因为只有iOS 9以上的iPad上面的软键盘左上方才会有这三个按钮:undo,redo,paste;
    2、输入字符达到最大20个不能再输入的时候,先点击paste按钮,然后点击undo按钮;


    QQ20160913-0.png

    华丽丽地crash了,当测试告诉我这个bug的时候我还莫名其妙,手贱打开iPad微信去看看其他的应用有没有限制字数的UITextField,果然发现了一个地方:个人信息-名字


    IMG_0041.jpg
    然后照着输到最大字数,然后那三个按钮反复点,果然,微信crash了!看来不是我一个人有这个问题啊。

    查看错误信息:

    **Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSBigMutableString substringWithRange:]: Range {20, 19} out of bounds; string length 20'**
    

    很清楚substringWithRange这个方法被调用的时候参数Range超界了,我没有调用这个方法,很显然是系统自己调用的。设置断点进去,仔细查看这个代理方法(- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string)的逻辑,才发现以前理解太肤浅了。
    1、输入简单字母的时候,比如当前内容是“123”, 输入4,那么代理方法触发时range参数是{3, 0},string是“4”,如果return yes,那么系统会调用substringWithRange这个方法改变{3, 0}位置的内容为“4”,由于{3, 0}本来就在末尾,所以这里就是简单的添加得到“1234”;
    2、如果是联想到了英语单词等情况,比如下面的点击候选框中的单词"keeps",那么此时range是{0, 2},string为“keeps”,return yes的话就会用“keeps”替换掉{0, 2}位置的内容“ke”得到“keeps”;


    QQ20160917-0.png

    3、点击删除按钮的时候,string为"",此时range为要删除的字符内容的位置,比如下面的range为{15, 1},return yes的话同样是替换此处的“w”为“”,从而完成删除;


    QQ20160917-1.png
    4、iPad上点击undo,redo,paste中的按钮都会触发该代理方法,比如点击paste时与前面1中情况比较类似,会在后面添加,点击undo的时候会撤销刚才的添加操作,此时与上面的操作3类似。如果刚才paste添加操作是range = {10, 2},string = "ww",那么就会在末尾添加“ww”,此时undo操作就会是range = {10, 2},string = “”,从而删掉刚才添加的“ww”。可以看出此时paste和undo操作range参数是一样的;
    那么bug为什么会发生呢?如果没有在这个代理方法里面加上上面的代码,是不会有bug的,无论你增删改,上面的range都不会超过textField.text的范围,也就是系统调用substringWithRange这个方法的时候并不会有问题。但是如果加上了上面的代码,表明我们并不允许字符数超过20,当text内容恰好长度为20的时候,此时点击paste,会调用这个代理方法(比如string为"ww",range为{20, 2}),但是会返回NO。然后你再点击undo时,此时的range也为{20, 2},string为“”,此时会返回yes,然后系统会调用substringWithRange方法,range超界,crash!
    找到原因了,这么写就不会crash了:
    - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
    {
        if (textField.text.length + string.length > 20) {
            return NO;
        }
        if (textField.text.length < range.location + range.length) {
            return NO;
        }
        
        return YES;
    }
    

    后面的代码是保证超界的时候返回NO,就不会去调用substringWithRange这个方法了。

    相关文章

      网友评论

        本文标题:限制UITextField字数的正确姿势

        本文链接:https://www.haomeiwen.com/subject/butvettx.html