Como limitar o comprimento do texto NSTextField e mantê-lo sempre em maiúsculas?

Precisa ter um NSTextField com um limite de texto de 4 caracteres no máximo e mostrar sempre em maiúsculas, mas não consegue descobrir uma boa maneira de conseguir isso. Eu tentei fazer isso através de uma binding com um método de validação, mas a validação só é chamada quando o controle perde o primeiro socorrista e isso não é bom.

De forma temporária, trabalhei observando a notificação NSControlTextDidChangeNotification no campo de texto e fazendo com que ele chamasse o método:

- (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]]; } } 

Mas esta certamente não é a melhor maneira de fazer isso. Alguma sugestão melhor?

Eu fiz como Graham Lee sugeriu e funciona bem, aqui está o código do formatador personalizado:

ATUALIZADO: Correção adicionada relatada por Dave Gallagher. Obrigado!

 @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 

Você já tentou append uma subclass NSFormatter personalizada?

No exemplo acima, onde comentei, isso é ruim:

 // Don't use: - (BOOL)isPartialStringValid:(NSString *)partialString newEditingString:(NSString **)newString errorDescription:(NSString **)error { if ((int)[partialString length] > maxLength) { *newString = nil; return NO; } } 

Use isso (ou algo parecido) em vez disso:

 // 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; } 

Ambos são methods NSFormatter. O primeiro tem um problema. Digamos que você limite a input de texto para 10 caracteres. Se você digitar caracteres em um por um em um NSTextField, ele funcionará bem e impedirá que os usuários ultrapassem 10 caracteres.

No entanto, se um usuário tiver que colar uma string de, digamos, 25 caracteres no campo de texto, o que acontecerá é algo assim:

1) O usuário irá colar no TextField

2) TextField aceitará a string de caracteres

3) TextField aplicará o formatador ao caractere “último” na cadeia de 25 comprimentos

4) O formatador faz material para o caractere “último” na cadeia de 25 comprimentos, ignorando o resto

5) TextField terminará com 25 caracteres, embora seja limitado a 10.

Isso porque, acredito, o primeiro método se aplica apenas ao “último caractere” typescript em um NSTextField. O segundo método mostrado acima se aplica a “todos os caracteres” typescripts no NSTextField. Portanto, é imune à exploração “colar”.

Eu descobri isso agora tentando quebrar meu aplicativo, e não sou um especialista em NSFormatter, então por favor me corrija se eu estiver errado. E muito obrigado a você carlosb por postar esse exemplo. Ele ajudou muito! 🙂

Esta implementação adota várias das sugestões comentadas acima. Notavelmente funciona corretamente com ligações de atualização contínua.

Além do que, além do mais:

  1. Ele implementa colar corretamente.

  2. Ele inclui algumas notas sobre como usar a class efetivamente em uma ponta sem mais subclasss.

O código:

 @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 

Eu precisava de um formatador para converter em maiúsculas para o Swift 4. Para referência, incluí-lo aqui:

 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?, for string: String, errorDescription error: AutoreleasingUnsafeMutablePointer?) -> Bool { obj?.pointee = string as AnyObject return true } } 

O NSFormatter personalizado sugerido por Graham Lee é a melhor abordagem.

Um kludge simples seria definir seu controlador de visualização como o delegado do campo de texto e, em seguida, bloquear qualquer edição que envolva não-letras maiúsculas ou torne o comprimento maior que 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; }