Как ограничить длину текста NSTextField и оставить его всегда в верхнем регистре?

StackOverflow https://stackoverflow.com/questions/827014

Вопрос

Необходимо иметь NSTextField с ограничением текста не более 4 символов и всегда показывать в верхнем регистре, но не может найти хороший способ достижения этого. Я пытался сделать это с помощью привязки с помощью метода проверки, но проверка вызывается только тогда, когда элемент управления теряет первый респондент, и это бесполезно.

Временно я заставил его работать, наблюдая за уведомлением NSControlTextDidChangeNotification в текстовом поле и заставляя его вызывать метод:

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

Но это, безусловно, не лучший способ сделать это. Есть лучшее предложение?

Это было полезно?

Решение

Я сделал так, как предложил Грэм Ли, и все работает нормально, вот код пользовательского форматера:

ОБНОВЛЕНО: добавлено исправление, о котором сообщил Дэйв Галлахер. Спасибо!

@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

Другие советы

В приведенном выше примере, где я прокомментировал, это плохо:

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

Используйте это (или что-то подобное) вместо этого:

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

Оба являются методами NSFormatter. У первого есть проблема. Допустим, вы ограничиваете ввод текста до 10 символов. Если вы вводите символы по одному в NSTextField, это будет работать нормально и не позволит пользователям выходить за пределы 10 символов.

Однако, если пользователь вставит строку, скажем, из 25 символов в текстовое поле, произойдет что-то вроде этого:

1) Пользователь вставит в TextField

2) TextField будет принимать строку символов

3) TextField применяет средство форматирования к " последнему " символ в строке длиной 25

4) Formatter выполняет работу с " последним " символ в строке длиной 25, игнорируя остальные

5) TextField будет содержать 25 символов, хотя он ограничен 10.

Это потому, что, как я полагаю, первый метод применим только к «самому последнему символу» набрал в NSTextField. Второй способ, показанный выше, применяется к «всем символам». набрал в NSTextField. Так что он невосприимчив к «пасте» эксплуатируют.

Я обнаружил это только сейчас, пытаясь сломать свое приложение, и я не эксперт по NSFormatter, поэтому, пожалуйста, исправьте меня, если я ошибаюсь. И большое спасибо вам Карлосб за публикацию этого примера. Это помогло МНОГО! :)

Вы пытались подключить пользовательский подкласс NSFormatter ?

Эта реализация принимает несколько предложений, прокомментированных выше. Примечательно, что он работает правильно с постоянно обновляемыми привязками.

Кроме того:

<Ол>
  • Он правильно выполняет вставку.

  • В него включены некоторые заметки о том, как эффективно использовать класс в кончике без дальнейшего создания подклассов.

  • Код:

    @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
    

    Мне нужен был Formatter для преобразования в прописные буквы для Swift 4. Для справки я включил его здесь:

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

    Лучше всего подходит нестандартный NSFormatter, предложенный Грэмом Ли.

    Простым клуджем было бы установить контроллер представления в качестве делегата текстового поля, а затем просто заблокировать любое редактирование, которое не в верхнем регистре или длина которого больше 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;
    }
    
    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top