Question

Besoin d'avoir un champ NSTextField avec une limite de texte de 4 caractères maximum et d'afficher toujours en majuscule, mais ne peut pas trouver un moyen efficace d'y parvenir. J'ai essayé de le faire via une liaison avec une méthode de validation, mais la validation n'est appelée que lorsque le contrôle perd le premier répondant, ce qui ne sert à rien.

Je l'ai fait fonctionner temporairement en observant la notification NSControlTextDidChangeNotification sur le champ de texte et en l'appelant la méthode:

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

Mais ce n’est sûrement pas la meilleure façon de le faire. Une meilleure suggestion?

Était-ce utile?

La solution

J'ai fait ce que Graham Lee avait suggéré et cela fonctionne bien, voici le code de formateur personnalisé:

UPDATED: Ajout du correctif signalé par Dave Gallagher. Merci!

@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

Autres conseils

Dans l'exemple ci-dessus où j'ai commenté, c'est mauvais:

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

Utilisez ceci (ou quelque chose comme ça) à la place:

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

Les deux sont des méthodes NSFormatter. Le premier a un problème. Disons que vous limitez la saisie de texte à 10 caractères. Si vous tapez des caractères un par un dans un champ NSText, tout fonctionnera bien et empêchera les utilisateurs de dépasser 10 caractères.

Toutefois, si un utilisateur devait coller une chaîne de, par exemple, 25 caractères dans le champ de texte, voici ce qui va se passer:

1) L'utilisateur collera dans TextField

2) TextField acceptera la chaîne de caractères

3) TextField appliquera le programme de formatage au "dernier". caractère dans la chaîne de 25 longueurs

4) Le formateur effectue la lecture jusqu'au "dernier". caractère dans la chaîne de 25 longueurs, en ignorant le reste

5) TextField se retrouvera avec 25 caractères, même s’il est limité à 10.

En effet, je pense que la première méthode ne s'applique qu'au "tout dernier caractère". tapé dans un NSTextField. La deuxième méthode présentée ci-dessus s’applique à "tous les caractères". tapé dans le NSTextField. Donc, il est immunisé contre la "pâte" exploiter.

Je viens de découvrir cela tout en essayant de casser ma candidature et je ne suis pas un expert en NSFormatter, alors corrigez-moi si je me trompe. Et merci beaucoup à vous carlosb d'avoir posté cet exemple. Cela a beaucoup aidé! :)

Avez-vous essayé de joindre une sous-classe NSFormatter personnalisée?

Cette mise en œuvre reprend plusieurs des suggestions commentées ci-dessus. En particulier, cela fonctionne correctement avec la mise à jour continue des liaisons.

En outre:

  1. Cela implémente coller correctement.

  2. Cela inclut quelques notes sur la façon d'utiliser efficacement la classe dans une nib sans autre sous-classement.

Le 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

J'avais besoin d'un formateur pour convertir en majuscule Swift 4. Pour référence, je l'ai inclus ici:

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

La coutume NSFormatter suggérée par Graham Lee est la meilleure approche.

Un simple problème serait de définir votre contrôleur de vue en tant que délégué du champ de texte, puis de bloquer toute modification impliquant une majuscule ou une longueur supérieure à 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;
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top