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:
Ele implementa colar corretamente.
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; }