Making autocomplete case-insensitive - objective-c

I'm implementing an autocomplete in cocoa for an OSX application and thus far I've got it all pinned down. The one hangup is that the autocomplete is case-sensitive and that's not really what I want/need. Ideally the autocomplete will be case INSENSITIVE. Relevant code below:
#implementation autocompleteController
- (void)viewDidLoad {
[super viewDidLoad];
self.textField.delegate = self;
}
-(void)controlTextDidChange:(NSNotification *)obj{
NSTextView * fieldEditor = [[obj userInfo] objectForKey:#"NSFieldEditor"];
if (self.isAutocompleting == NO && !self.backspaceKey) {
self.isAutocompleting = YES;
self.lastEntry = [[[fieldEditor string] capitalizedString] copy];
[fieldEditor complete:nil];
self.isAutocompleting = NO;
}
if (self.backspaceKey) {
self.backspaceKey = NO;
}
}
-(NSArray *)control:(NSControl *)control textView:(NSTextView *)textView completions:(NSArray *)words forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index{
NSMutableArray * suggestions = [NSMutableArray array];
NSArray * possibleStrings = #[#"TEST", #"ABC", #"abc", #"amauroy", #"AMA", #"amazing"];
if (!self.lastEntry || !possibleStrings) {
return nil;
}
for (NSString * string in possibleStrings) {
if ([string hasPrefix:self.lastEntry]) {
[suggestions addObject:string];
}
}
return suggestions;
}
-(BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector{
if (commandSelector == #selector(deleteBackward:)) {
self.backspaceKey = YES;
}
return NO;
}
#end

As Pro Blaster points out, the problem is with the following line:
if ([string hasPrefix:self.lastEntry]) {
Your autocompletion is case-sensitive because -hasPrefix: is case-sensitive. One approach is to convert everything to lower case (upper case would also work, of course). Another is to write a case-insensitive version of -hasPrefix: and add it to NSString using a category, like this:
#interface NSString (autocomplete)
- (BOOL)hasPrefixIgnoringCase:(NSString*)aString;
#end;
#implementation NSString (autocomplete)
- (BOOL)hasPrefixIgnoringCase:(NSString*)aString
{
NSRange *prefix = [self rangeOfString:aString options:NSCaseInsensitiveSearch];
return prefix.location == 0 && prefix.length == aString.length;
}
#end
Then use that method in your code:
if ([string hasPrefixIgnoringCase:self.lastEntry]) {
Note: The provided code is untested. The concept is sound, but you may find a syntax error or two.

I did this once.
You would do so by replacing :
for (NSString * string in possibleStrings) {
if ([string hasPrefix:self.lastEntry]) {
[suggestions addObject:string];
}
}
return suggestions
with:
for (NSString * string in possibleStrings) {
if ([[string lowercaseString] hasPrefix:[self.lastEntry lowercaseString]]) {
[suggestions addObject:string];
}
}
return suggestions;

Related

How to check if an object is the last object on NSArray

How can I check if an object in the last object on an NSArray?
I've tried:
if ([currentStore isEqual:[Stores lastObject]])
{
//Code
}
but it didn't work.
Any idea?
Thanks!
or try this
BOOL lastElement = false;
NSUInteger index = [stores indexOfObject:currentStore];
if (index != NSNotFound)
{
lastElement = (index == [stores count] - 1);
}
Bit modified try this:
NSUInteger index = [stores indexOfObject:currentStore];
if (index == ([stores count]-1))
{
NSLog(#"Yes its Last");
}
If you didn't override isEqual method, the base class implementation of NSObject::isEqual only check if both pointers points to the same address.
This excellent article http://nshipster.com/equality/ explain objc equality principles
The below sample logs - Testing Stores - works fine
#interface Stores : NSObject
#property (strong, nonatomic) NSString* name;
- (instancetype) initWithName:(NSString*) name;
#end
#implementation Stores
- (instancetype) initWithName:(NSString*) name;
{
_name = name;
return self;
}
- (BOOL)isEqualToStores:(Stores*) Stores
{
if (!Stores)
return NO;
if (![_name isEqualToString:Stores.name] )
return NO;
return YES;
}
- (BOOL)isEqual:(id)object
{
if (self == object)
{
return YES;
}
if (![object isKindOfClass:[Stores class]])
{
return NO;
}
return [self isEqualToStores:(Stores *)object];
}
#end
-(void) testStores
{
Stores* last = [[Stores alloc] initWithName:#"5"];
NSArray* arr = #[
[[Stores alloc] initWithName:#"1"],
[[Stores alloc] initWithName:#"2"],
[[Stores alloc] initWithName:#"3"],
[[Stores alloc] initWithName:#"4"],
[[Stores alloc] initWithName:#"5"]
//last
];
if ([last isEqual:[arr lastObject]])
{
NSLog(#"Testing Stores - works fine");
}
else
{
NSLog(#"Testing Stores - opps!?1?!?");
}
}

Strange NSFormatter behavior

I have TextField with value binded to Document's serverAddress property (readwrite, copy), and TextField formatter delegate connected with ServerAddressFormatter object in XIB.
It's actually working with input like 127.0.0.1:8080, but as soon as I put something without : TextField clear itself completely.
Here's ServerAddressFormatter implementation:
#implementation ServerAddressFormatter
- (NSString *) stringForObjectValue:(NSArray *)obj {
if ([obj isKindOfClass:[NSArray class]]) {
return [obj componentsJoinedByString:#":"];
} else {
return #"";
}
}
- (BOOL)getObjectValue:(out id *)anObject
forString:(NSString *)string
errorDescription:(out NSString **)error {
int i;
for (i = (int) ([string length] == 0 ? 0 : [string length] - 1); i > 0; i--) {
if ([string characterAtIndex:i] == ':') break;
}
if (i == 0) {
*anObject = #[string]; // if I put string, #"100" here it's working fine
} else {
*anObject = #[[string substringToIndex:i], [string substringFromIndex:i+1]];
}
return YES;
}
#end

How to restrict numbers and special characters in objective-c [duplicate]

This question already has answers here:
Restrict NSTextField to only allow numbers
(10 answers)
Closed 8 years ago.
In textfield I want to restrict numbers like (1234567890) and special characters but I want to allow alphanumeric characters. How I am suppose to do this?
Use the UITextField delegate method
textField:shouldChangeCharactersInRange:replacementString:
To check the string that is about to be replaced, if you allow it then return yes if not then return no.
Here is some more information.
Apple UITextField Delegate
try following code
+ (BOOL)isNumber:(NSString *)value {
if ( (value == nil) || ([#"" isEqualToString:value]) ) {
return NO;
}
int l = [value length];
BOOL b = NO;
for (int i = 0; i < l; i++) {
NSString *str =
[[value substringFromIndex:i]
substringToIndex:1];
const char *c =
[str cStringUsingEncoding:
NSASCIIStringEncoding];
if ( c == NULL ) {
b = NO;
break;
}
if ((c[0] >= 0x30) && (c[0] <= 0x39)) {
b = YES;
} else {
b = NO;
break;
}
}
if (b) {
return YES;
} else {
return NO;
}
}
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if ( (string != nil) && (string != #"") ) {
if (![self isNumber:string]) {
return NO;
}
}
return YES;
}
You need to write a NSFormatter and assign it to your text field. Here an example implementation of a such NSFormatter which uses a NSRegularExpression to validate the NSTextField contents.
#interface XXNameElementFormatter : NSFormatter
#end
#implementation HcNameElementFormatter {
NSRegularExpression *_re;
}
- (id)init {
self = [super init];
if (self) {
[self initRegularExpression];
}
return self;
}
- (void)awakeFromNib
{
[self initRegularExpression];
}
- (void)initRegularExpression
{
NSError *reError;
_re = [NSRegularExpression regularExpressionWithPattern:#"^[a-z]*$" options:NSRegularExpressionCaseInsensitive error:&reError];
NSAssert(_re != nil, #"Error in regular expression, error: %#", reError);
}
- (NSString *)stringForObjectValue:(id)obj
{
return obj;
}
- (BOOL)getObjectValue:(out __autoreleasing id *)obj forString:(NSString *)string errorDescription:(out NSString *__autoreleasing *)error
{
*obj = string;
return YES;
}
- (BOOL)isPartialStringValid:(NSString *__autoreleasing *)partialStringPtr proposedSelectedRange:(NSRangePointer)proposedSelRangePtr originalString:(NSString *)origString originalSelectedRange:(NSRange)origSelRange errorDescription:(NSString *__autoreleasing *)error
{
NSParameterAssert(partialStringPtr != nil);
NSString *partialString = *partialStringPtr;
NSRange firstMatch = [_re rangeOfFirstMatchInString:*partialStringPtr options:0 range:NSMakeRange(0, partialString.length)];
return firstMatch.location != NSNotFound;
}
#end

Objective C newbie

I'm taking a class and we're working on a Calculator program. My background is in C++. I am taking a RPN calculator entry of 3 enter sqrt and need to display it as sqrt(3) in my descriptionOfProgram method, which is new, including associated property below. Here's the class so far. Search for "xcode" to find my issues. Any ideas? I'm not very good at the basic objective c classes, but I'm trying to learn. Here's a summary:
it's complaining about my boolean. I'm not sure why. I did this in a different class and it worked fine.
it's looking for a { I don't see it
it doesn't like my use of the key. I'm unclear on how to get the key's contents I think is the problem.
It wants ] but I'm not seeing why
skipped
It expected } at #end
Hope you can help! Thanks!
//
// CalculatorBrain.m
// Calculator
//
// Created by Michele Cleary on 2/25/12.
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
#import "CalculatorBrain.h"
#interface CalculatorBrain()
#property (nonatomic, strong) NSMutableArray *programStack;
#property (nonatomic, strong) NSDictionary *testVariable;
#property (nonatomic) BOOL numberHandledNextOperation;
- (double) convertRadianToDegree: (double) radian;
#end
#implementation CalculatorBrain
#synthesize programStack = _programStack;
#synthesize testVariable = _testVariable;
#synthesize numberHandledNextOperation = _numberHandledNextOperation;
- (NSMutableArray *)programStack
{
if (_programStack == nil) _programStack = [[NSMutableArray alloc] init];
return _programStack;
}
//- (void)setOperandStack:(NSMutableArray *)operandStack
//{
// _operandStack = operandStack;
//}
- (void)pushOperand:(double)operand
{
[self.programStack addObject:[NSNumber numberWithDouble:operand]];
}
- (double)performOperation:(NSString *)operation
{
[self.programStack addObject:operation];
return[CalculatorBrain runProgram:self.program];
}
- (id)program
{
return [self.programStack copy];
}
+ (NSString *)descriptionOfProgram:(id)program
{
self.numberHandledNextOperation = NO; //1. this is a problem with xcode: member reference type struct objc_class * is a pointer; maybe you meant to use ->
NSMutableSet * displayDescrip = [[NSMutableSet alloc] init];
for(id foundItemKey in program)
{
if ([foundItemKey isKindOfClass:[NSString class]])
//operator or variable
{
if ([foundItemKey isEqualToString:#"sin"]&&(!self.numberHandledNextOperation))
{ //2. xcode says To match this {.
NSObject *nextObj = [program objectForKey:(foundItemKey+1); //3. xcode doesn't like this: arithmetic on pointer to interface id which is not a constant size in non-fragile ABI
//[displayDescrip addObject:foundItemKey];
}
else if ([foundItemKey isEqualToString:#"cos"])
{
//[displayDescrip addObject:foundItemKey];
}
else if ([foundItemKey isEqualToString:#"sqrt"])
{
//[displayDescrip addObject:foundItemKey];
}
else if ([foundItemKey isEqualToString:#"Ï€"])
{
//[displayDescrip addObject:foundItemKey];
}
else if (![CalculatorBrain isOperationName:foundItemKey])
{
//variable
//[displayDescrip addObject:foundItemkey];
}
else if (foundItemKey isKindOfClass:[NSNumber class]) //4. xcode expected ]
{
//number
//if next object is operation
if(isOperation([program objectForKey:(foundItemKey+1)))
{
numberHandledNextOperation = YES;
if(isOperationSpecial([program objectForKey:(foundItemKey+1)))
{ //sin or cos or sqrt need parentheses
//[displayDescrip addObject:(foundItemKey+1)];
//[displayDescrip addObject:#"("];
//[displayDescrip addObject:foundItemKey];
//[displayDescrip addObject:#")"];
}
else
{ //regular operation + - / *
//[displayDescrip addObject:(foundItemKey+1)];
//[displayDescrip addObject:(foundItemKey)];
}
numberHandledNextOperation = YES;
} //if
} //else if
} //if
} //for
//not sure if I need this next thing
//NSSet * returnedVarNames = [varNames copy];
//return returnedVarNames;
return #"implement this in Assignment 2";
}
+ (double)runProgram:(id)program
{
NSMutableArray *stack;
if ([program isKindOfClass:[NSArray class]]) {
stack = [program mutableCopy];
}
return [self popOperandOffStack:stack];
}
+ (double)runProgram:(id)program usingVariableValues:(NSDictionary *)variableValues
{
NSMutableArray *stack;
if ([program isKindOfClass:[NSArray class]]) {
stack = [program mutableCopy];
}
if(variableValues)
{
int numItemsDisplayed = [stack count];
for (int count = 0; count < numItemsDisplayed; count++)
{
id foundItem = [stack objectAtIndex:count];
if ([foundItem isKindOfClass:[NSString class]])
{
NSString * var = [variableValues objectForKey:foundItem];
if(var)
{
[stack replaceObjectAtIndex:count withObject:[NSNumber numberWithDouble:[var doubleValue]]];
}
}
}
}
return [self popOperandOffStack:stack];
}
+ (double)popOperandOffStack:(NSMutableArray *)stack
{
double result = 0;
id topOfStack = [stack lastObject];
if (topOfStack) [stack removeLastObject];
if([topOfStack isKindOfClass:[NSNumber class]]){ //number
result = [topOfStack doubleValue];
}
else if ([topOfStack isKindOfClass:[NSString class]]){ //string operation
NSString *operation = topOfStack;
if ([operation isEqualToString:#"+"]) {
result = [self popOperandOffStack:stack] + [self popOperandOffStack:stack];
}else if ([operation isEqualToString:#"*"]) {
result = [self popOperandOffStack:stack] * [self popOperandOffStack:stack];
}else if ([operation isEqualToString:#"/"]) {
double divisor = [self popOperandOffStack:stack];
if (divisor)
result = [self popOperandOffStack:stack] / divisor;
}else if ([operation isEqualToString:#"-"]) {
double subtrahend = [self popOperandOffStack:stack];
result = [self popOperandOffStack:stack] - subtrahend;
}else if ([operation isEqualToString:#"sin"]) {
result = result = (sin([self popOperandOffStack:stack])); //(sin([self convertRadianToDegree:[self popOperandOffStack:stack]]));
}else if ([operation isEqualToString:#"cos"]) {
result = (cos([self popOperandOffStack:stack]));
}else if ([operation isEqualToString:#"sqrt"]) {
result = (sqrt([self popOperandOffStack:stack]));
}else if ([operation isEqualToString:#"π"]) {
result = M_PI;
}else{
result = 0;
}
}
return result;
}
+ (NSSet *)variablesUsedInProgram:(id)program
{
NSMutableSet * varNames = [[NSMutableSet alloc] init];
for(id foundItem in program)
{
if ([foundItem isKindOfClass:[NSString class]])
{
if (![CalculatorBrain isOperationName:foundItem])
{
[varNames addObject:foundItem];
}
}
}
NSSet * returnedVarNames = [varNames copy];
return returnedVarNames;
}
+ (BOOL)isOperationName:(NSString *)foundItem
{
NSSet *myOperationSet = [NSSet setWithObjects:#"sqrt", #"sin", #"cos", #"π", #"+", #"-", #"*", #"/", nil];
return([myOperationSet containsObject:(foundItem)]);
}
- (NSString *)description
{
return [NSString stringWithFormat:#"stack = %#", self.programStack];
}
-(double) convertRadianToDegree: (double) radian;
{
return M_PI*2*radian/360;
}
#end //6. xcode expected }
+ (NSString *)descriptionOfProgram:(id)program
Do you actually want descriptionOfProgram a class + method ? If yes, it is more like a static method in C++. It doesn't belong to any particular instance of a class. There is no hidden parameter of constant pointer to the current instance is passed.

How to limit NSTextField text length and keep it always upper case?

Need to have an NSTextField with a text limit of 4 characters maximum and show always in upper case but can't figure out a good way of achieving that. I've tried to do it through a binding with a validation method but the validation only gets called when the control loses first responder and that's no good.
Temporarly I made it work by observing the notification NSControlTextDidChangeNotification on the text field and having it call the method:
- (void)textDidChange:(NSNotification*)notification {
NSTextField* textField = [notification object];
NSString* value = [textField stringValue];
if ([value length] > 4) {
[textField setStringValue:[[value uppercaseString] substringWithRange:NSMakeRange(0, 4)]];
} else {
[textField setStringValue:[value uppercaseString]];
}
}
But this surely isn't the best way of doing it. Any better suggestion?
I did as Graham Lee suggested and it works fine, here's the custom formatter code:
UPDATED: Added fix reported by Dave Gallagher. Thanks!
#interface CustomTextFieldFormatter : NSFormatter {
int maxLength;
}
- (void)setMaximumLength:(int)len;
- (int)maximumLength;
#end
#implementation CustomTextFieldFormatter
- (id)init {
if(self = [super init]){
maxLength = INT_MAX;
}
return self;
}
- (void)setMaximumLength:(int)len {
maxLength = len;
}
- (int)maximumLength {
return maxLength;
}
- (NSString *)stringForObjectValue:(id)object {
return (NSString *)object;
}
- (BOOL)getObjectValue:(id *)object forString:(NSString *)string errorDescription:(NSString **)error {
*object = string;
return YES;
}
- (BOOL)isPartialStringValid:(NSString **)partialStringPtr
proposedSelectedRange:(NSRangePointer)proposedSelRangePtr
originalString:(NSString *)origString
originalSelectedRange:(NSRange)origSelRange
errorDescription:(NSString **)error {
if ([*partialStringPtr length] > maxLength) {
return NO;
}
if (![*partialStringPtr isEqual:[*partialStringPtr uppercaseString]]) {
*partialStringPtr = [*partialStringPtr uppercaseString];
return NO;
}
return YES;
}
- (NSAttributedString *)attributedStringForObjectValue:(id)anObject withDefaultAttributes:(NSDictionary *)attributes {
return nil;
}
#end
Have you tried attaching a custom NSFormatter subclass?
In the above example where I commented, this is bad:
// Don't use:
- (BOOL)isPartialStringValid:(NSString *)partialString
newEditingString:(NSString **)newString
errorDescription:(NSString **)error
{
if ((int)[partialString length] > maxLength)
{
*newString = nil;
return NO;
}
}
Use this (or something like it) instead:
// Good to use:
- (BOOL)isPartialStringValid:(NSString **)partialStringPtr
proposedSelectedRange:(NSRangePointer)proposedSelRangePtr
originalString:(NSString *)origString
originalSelectedRange:(NSRange)origSelRange
errorDescription:(NSString **)error
{
int size = [*partialStringPtr length];
if ( size > maxLength )
{
return NO;
}
return YES;
}
Both are NSFormatter methods. The first one has an issue. Say you limit text-entry to 10 characters. If you type characters in one-by-one into an NSTextField, it'll work fine and prevent users from going beyond 10 characters.
However, if a user was to paste a string of, say, 25 characters into the Text Field, what'll happen is something like this:
1) User will paste into TextField
2) TextField will accept the string of characters
3) TextField will apply the formatter to the "last" character in the 25-length string
4) Formatter does stuff to the "last" character in the 25-length string, ignoring the rest
5) TextField will end up with 25 characters in it, even though it's limited to 10.
This is because, I believe, the first method only applies to the "very last character" typed into an NSTextField. The second method shown above applies to "all characters" typed into the NSTextField. So it's immune to the "paste" exploit.
I discovered this just now trying to break my application, and am not an expert on NSFormatter, so please correct me if I'm wrong. And very much thanks to you carlosb for posting that example. It helped a LOT! :)
This implementation adopts several of the suggestions commented on above. Notably it works correctly with continuously updating bindings.
In addition:
It implements paste correctly.
It includes some notes on how to use the class effectively in a nib
without further subclassing.
The code:
#interface BPPlainTextFormatter : NSFormatter {
NSInteger _maxLength;
}
/*
Set the maximum string length.
Note that to use this class within a Nib:
1. Add an NSFormatter as a Custom Formatter.
2. In the Identity inspector set the Class to BPPlainTextFormatter
3. In user defined attributes add Key Path: maxLength Type: Number Value: 30
Note that rather than attaching formatter instances to individual cells they
can be positioned in the nib Objects section and referenced by numerous controls.
A name, such as Plain Text Formatter 100, can be used to identify the formatters max length.
*/
#property NSInteger maxLength;
#end
#implementation BPPlainTextFormatter
#synthesize maxLength = _maxLength;
- (id)init
{
if(self = [super init]){
self.maxLength = INT_MAX;
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
// support Nib based initialisation
self = [super initWithCoder:aDecoder];
if (self) {
self.maxLength = INT_MAX;
}
return self;
}
#pragma mark -
#pragma mark Textual Representation of Cell Content
- (NSString *)stringForObjectValue:(id)object
{
NSString *stringValue = nil;
if ([object isKindOfClass:[NSString class]]) {
// A new NSString is perhaps not required here
// but generically a new object would be generated
stringValue = [NSString stringWithString:object];
}
return stringValue;
}
#pragma mark -
#pragma mark Object Equivalent to Textual Representation
- (BOOL)getObjectValue:(id *)object forString:(NSString *)string errorDescription:(NSString **)error
{
BOOL valid = YES;
// Be sure to generate a new object here or binding woe ensues
// when continuously updating bindings are enabled.
*object = [NSString stringWithString:string];
return valid;
}
#pragma mark -
#pragma mark Dynamic Cell Editing
- (BOOL)isPartialStringValid:(NSString **)partialStringPtr
proposedSelectedRange:(NSRangePointer)proposedSelRangePtr
originalString:(NSString *)origString
originalSelectedRange:(NSRange)origSelRange
errorDescription:(NSString **)error
{
BOOL valid = YES;
NSString *proposedString = *partialStringPtr;
if ([proposedString length] > self.maxLength) {
// The original string has been modified by one or more characters (via pasting).
// Either way compute how much of the proposed string can be accommodated.
NSInteger origLength = origString.length;
NSInteger insertLength = self.maxLength - origLength;
// If a range is selected then characters in that range will be removed
// so adjust the insert length accordingly
insertLength += origSelRange.length;
// Get the string components
NSString *prefix = [origString substringToIndex:origSelRange.location];
NSString *suffix = [origString substringFromIndex:origSelRange.location + origSelRange.length];
NSString *insert = [proposedString substringWithRange:NSMakeRange(origSelRange.location, insertLength)];
#ifdef _TRACE
NSLog(#"Original string: %#", origString);
NSLog(#"Original selection location: %u length %u", origSelRange.location, origSelRange.length);
NSLog(#"Proposed string: %#", proposedString);
NSLog(#"Proposed selection location: %u length %u", proposedSelRangePtr->location, proposedSelRangePtr->length);
NSLog(#"Prefix: %#", prefix);
NSLog(#"Suffix: %#", suffix);
NSLog(#"Insert: %#", insert);
#endif
// Assemble the final string
*partialStringPtr = [[NSString stringWithFormat:#"%#%#%#", prefix, insert, suffix] uppercaseString];
// Fix-up the proposed selection range
proposedSelRangePtr->location = origSelRange.location + insertLength;
proposedSelRangePtr->length = 0;
#ifdef _TRACE
NSLog(#"Final string: %#", *partialStringPtr);
NSLog(#"Final selection location: %u length %u", proposedSelRangePtr->location, proposedSelRangePtr->length);
#endif
valid = NO;
}
return valid;
}
#end
I needed a Formatter to convert to uppercase for Swift 4. For reference I've included it here:
import Foundation
class UppercaseFormatter : Formatter {
override func string(for obj: Any?) -> String? {
if let stringValue = obj as? String {
return stringValue.uppercased()
}
return nil
}
override func getObjectValue(_ obj: AutoreleasingUnsafeMutablePointer<AnyObject?>?, for string: String, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {
obj?.pointee = string as AnyObject
return true
}
}
Swift version of Carlos Barbosa answer, if someone needs it.
Example of usage:
myTextField.formatter = CustomTextFieldFormatter(maxLength: 10, isUppercased: true)
class CustomTextFieldFormatter: Formatter {
var maxLength: UInt
var isUppercased: Bool
init(maxLength: UInt, isUppercased: Bool) {
self.maxLength = maxLength
self.isUppercased = isUppercased
super.init()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func string(for obj: Any?) -> String? {
return obj as? String
}
override func getObjectValue(_ obj: AutoreleasingUnsafeMutablePointer<AnyObject?>?, for string: String, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {
obj?.pointee = string as AnyObject
return true
}
override func isPartialStringValid(_ partialStringPtr: AutoreleasingUnsafeMutablePointer<NSString>, proposedSelectedRange proposedSelRangePtr: NSRangePointer?, originalString origString: String, originalSelectedRange origSelRange: NSRange, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {
if partialStringPtr.pointee.length > maxLength {
return false
}
if isUppercased && partialStringPtr.pointee != partialStringPtr.pointee.uppercased as NSString {
partialStringPtr.pointee = partialStringPtr.pointee.uppercased as NSString
return false
}
return true
}
override func attributedString(for obj: Any, withDefaultAttributes attrs: [NSAttributedString.Key : Any]? = nil) -> NSAttributedString? {
return nil
}
}
The custom NSFormatter that Graham Lee suggested is the best approach.
A simple kludge would be to set your view controller as the text field's delegate then just block any edit that involves non-uppercase or makes the length longer than 4:
- (BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string
{
NSMutableString *newValue = [[textField.text mutableCopy] autorelease];
[newValue replaceCharactersInRange:range withString:string];
NSCharacterSet *nonUppercase =
[[NSCharacterSet uppercaseLetterCharacterSet] invertedSet];
if ([newValue length] > 4 ||
[newValue rangeOfCharacterFromSet:nonUppercase].location !=
NSNotFound)
{
return NO;
}
return YES;
}