0

I am trying to prevent the user from entering any lowercase characters into a UITextView.

I have created a #define character set of acceptable characters

#define VALID_CHARACTERS @"ABCDEFGHIJKLMNOPQRSTUVWYXZ1234567890"

Now I would like to know how to use this to return no in the UITextView delegate when the user tries to enter a character that dose not match my VALID_CHARACTERS.

halfer
  • 19,824
  • 17
  • 99
  • 186
HurkNburkS
  • 5,492
  • 19
  • 100
  • 183
  • 1
    Instead of preventing users from typing lower, convert to upper always Gives a much better user experience. http://stackoverflow.com/a/2027227/366346 – GoodSp33d Apr 13 '14 at 07:37
  • In the answer below that "Even with the autocapitalizationType set to UITextAutocapitalizationTypeAllCharacters, the user can still press caps to release the caps lock" – HurkNburkS Apr 13 '14 at 07:41
  • Is your question about UITextField or UITextView (you mention both)? And do you want to prevent lower case letters or only allow upper case letters and digits? What about symbols, spaces, emojis and other stuff? – Martin R Apr 13 '14 at 07:42
  • whoops, fixed.. UITextView.. sorry I am so used to used UITextField its force of habbit. I want to only allow upper case and digits.. nothing else – HurkNburkS Apr 13 '14 at 07:48
  • "Eh", or let me say "Äh", why is an uppercase A-Umlaut no uppercase character? And what is `+uppercaseLettersCharacterSet` for? – Amin Negm-Awad Apr 13 '14 at 08:25

3 Answers3

2

I suggest using textView:shouldChangeTextInRange:replacementText: and returning NO if the replacement text contains invalid characters.

Edit (in response to your comment):

// Goal: Remove all the non-valid characters from the replacement string
// then see if the string is the same as the original replacement string;
// if it is, then the string is valid and return YES, else return NO

 - (BOOL)textField:(UITextField *)theTextField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {    

    // First step - define a character set with the valid characters
    NSMutableCharacterSet *validSet = [NSMutableCharacterSet characterSetWithCharactersInString:VALID_CHARACTERS];

    // Second step - define a character set with the inverse, i.e invalid characters
    NSCharacterSet *invalidSet = [validSet invertedSet];

    // Then remove all invalid characters by separating the string into components
    // separated by the invalid characters using componentsSeparatedByCharactersInSet:
    // and then rejoining the set using componentsJoinedByString: so that it now only
    // contains valid characters
    NSString *filteredString = [[string componentsSeparatedByCharactersInSet:invalidSet] componentsJoinedByString:@""];

    // Compare that filtered string with the original string then see if its
    // the same as the replacement string; if the same (i.e. no invalid characters
    // have been removed), return yes, if not, return no.
    return ([filteredString isEqualToString:string]);

}
Lyndsey Scott
  • 37,080
  • 10
  • 92
  • 128
2

To restrict the possible input characters for a text view, implement the text view delegate like this:

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{    
    static NSString *validChars = @"ABCDEFGHIJKLMNOPQRSTUVWYXZ1234567890\n";
    NSCharacterSet *validSet = [NSCharacterSet characterSetWithCharactersInString:validChars];
    if ([[text stringByTrimmingCharactersInSet:validSet] length] > 0)
        return NO;
    return YES;
}

\n in validChars is for the RETURN key (which you may or not may want to allow).

As suggested in the comments, you could convert lower case letters to upper case automatically:

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{    
    text = [text uppercaseString];
    static NSString *validChars = @"ABCDEFGHIJKLMNOPQRSTUVWYXZ1234567890\n";
    NSCharacterSet *validSet = [NSCharacterSet characterSetWithCharactersInString:validChars];
    if ([[text stringByTrimmingCharactersInSet:validSet] length] > 0)
        return NO;
    textView.text = [textView.text stringByReplacingCharactersInRange:range withString:text];
    return NO;
}

To allow also uppercase letters and digits from other languages, change the definition of validSet to

NSMutableCharacterSet *validSet = [NSMutableCharacterSet uppercaseLetterCharacterSet];
[validSet formUnionWithCharacterSet:[NSCharacterSet decimalDigitCharacterSet]];
[validSet formUnionWithCharacterSet:[NSCharacterSet newlineCharacterSet]];

Since the text view delegate is called frequently, you can improve this by using GCD to compute the set of valid characters only once:

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
    static NSCharacterSet *validSet;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSMutableCharacterSet *tmpSet = [NSMutableCharacterSet uppercaseLetterCharacterSet];
        [tmpSet formUnionWithCharacterSet:[NSCharacterSet decimalDigitCharacterSet]];
        [tmpSet formUnionWithCharacterSet:[NSCharacterSet newlineCharacterSet]];
        validSet = [tmpSet copy];
    });
    text = [text uppercaseString];
    if ([[text stringByTrimmingCharactersInSet:validSet] length] > 0)
        return NO;
    textView.text = [textView.text stringByReplacingCharactersInRange:range withString:text];
    return NO;
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
0

Simplest solution:

Use UITextView delegate method:

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text;

Method implementation:

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
    if ([text rangeOfCharacterFromSet:[NSCharacterSet lowercaseLetterCharacterSet]].location != NSNotFound) {
        textView.text = [textView.text stringByReplacingCharactersInRange:range withString:text.uppercaseString];
        return NO;
    }

    return YES;
}
Aron Balog
  • 574
  • 6
  • 9