2009-05-05 5 views
24

È necessario disporre di un NSTextField con un limite di testo di massimo 4 caratteri e mostrare sempre in maiuscolo, ma non è possibile trovare un buon modo per ottenerlo. Ho provato a farlo attraverso un'associazione con un metodo di convalida, ma la convalida viene chiamata solo quando il controllo perde il primo risponditore e non va bene.Come limitare la lunghezza del testo NSTextField e tenerla sempre in maiuscolo?

temporaneamente Ho fatto il lavoro osservando la NSControlTextDidChangeNotification notifica sul campo di testo e averlo chiamare il metodo:

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

Ma questo sicuramente non è il modo migliore di farlo. Qualche suggerimento migliore?

risposta

43

ho fatto come Graham Lee suggerito e funziona bene, ecco il codice formattatore personalizzato:

AGGIORNATO: Aggiunta la correzione riportata da Dave Gallagher. Grazie!

@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 
+1

Dovresti accettare la risposta di Graham, dato che ti ha indicato nella direzione corretta! Buon lavoro! – Jab

+1

Grazie per aver trovato il tempo di tornare e pubblicare l'intera soluzione! –

+1

Ho scoperto un errore con il codice sopra. Esiste un potenziale exploit che utilizza isPartialStringValid: newEditingString: errorDescription :. Se inserisci un testo in un campo NSText, carattere per carattere sulla tastiera, non si verificheranno problemi. Tuttavia, se si incolla una stringa di 2 o più caratteri nel campo di testo, eseguirà la convalida sull'ultimo carattere immesso, ma ignorerà tutti i caratteri inseriti in precedenza. Questo può portare a inserire più caratteri di quelli consentiti nel campo di testo. Qui di seguito pubblicherò altri dettagli e una soluzione (spazio esaurito qui). –

12

Hai provato a collegare una sottoclasse personalizzata NSFormatter?

-1

Il NSFormatter personalizzato suggerito da Graham Lee è l'approccio migliore.

Un semplice ripiego potrebbe essere quella di impostare la visualizzazione del controller come delegato del campo di testo poi basta bloccare qualsiasi modifica che coinvolge non maiuscolo o rende la lunghezza più lungo di 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; 
} 
+6

che esiste solo in iOS con UITextField. Sta lavorando con un NSTextField e questo metodo non esiste. –

11

Nel precedente esempio, dove ho commentato, questo è male:

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

Utilizzare questo (o qualcosa di simile), invece:

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

Entrambi sono metodi NSFormatter. Il primo ha un problema. Supponi di limitare l'immissione di testo a 10 caratteri. Se digiti caratteri uno alla volta in un NSTextField, funzionerà correttamente e impedirà agli utenti di andare oltre i 10 caratteri.

Tuttavia, se un utente è stato quello di pasta una serie di, diciamo, 25 caratteri nel campo di testo, cosa succederà è qualcosa di simile:

1) L'utente si incolla nella TextField

2) TextField accetterà la stringa di caratteri

3) TextField applicherà il formattatore al carattere "ultima" nella stringa di 25 di lunghezza

4) formatter fa cose all ' "ultimo "Carattere nella stringa 25 di lunghezza, ignorando il resto

5) TextField finirà con 25 caratteri in esso, anche se è limitato a 10.

Questo perché, ritengo, il primo metodo unico si applica al "carattere ultimo" digitato in un campo NSText. Il secondo metodo mostrato sopra si applica a "tutti i caratteri" digitati nel campo NSText. Quindi è immune all'exploit "incolla".

Ho scoperto questo solo ora cercando di rompere la mia domanda, e non sono un esperto di NSFormatter, quindi per favore correggimi se sbaglio.E grazie mille anche a te carlosb per aver pubblicato quell'esempio. Ha aiutato un sacco! :)

+5

L'utente non ha nemmeno bisogno di incollare. Un'associazione di chiavi personalizzate definita dall'utente (vedere http://www.hcs.harvard.edu/~jrus/site/cocoa-text.html per i dettagli) può inserire qualsiasi stringa e un singolo punto di codice che si trova all'esterno di Basic Multilingual Il piano sarà composto da più "caratteri" nel senso a due byte (UTF-16) di Cocoa. –

+0

Grazie per questo fantastico articolo Peter! –

9

Questa implementazione adotta alcuni dei suggerimenti commentati sopra. In particolare funziona correttamente con l'aggiornamento continuo dei collegamenti.

Inoltre:

  1. Essa attua correttamente pasta.

  2. Include alcune note su come utilizzare efficacemente la classe in un pennino senza ulteriore sottoclasse.

Il codice:

@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 
+0

merita l'upvot –

0

avevo bisogno di un Formatter per convertire in maiuscolo per Swift 4. Per riferimento ho incluso qui:

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