26

需要有一个 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]];
  }
}

但这肯定不是最好的方法。有更好的建议吗?

4

7 回答 7

49

我按照 Graham Lee 的建议做了,效果很好,这是自定义格式化程序代码:

更新:添加了 Dave Gallagher 报告的修复。谢谢!

@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
于 2009-05-06T14:56:59.383 回答
13

您是否尝试过附加自定义NSFormatter子类?

于 2009-05-05T22:30:55.557 回答
12

在上面我评论的例子中,这很糟糕:

// 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 专家,所以如果我错了,请纠正我。非常感谢你carlosb发布这个例子。它帮助了很多!:)

于 2010-05-03T02:14:18.387 回答
10

这个实现采用了上面评论过的几个建议。值得注意的是,它可以在不断更新绑定的情况下正常工作。

此外:

  1. 它正确地实现了粘贴。

  2. 它包括一些关于如何在不进一步子类化的情况下在 nib 中有效使用类的注释。

编码:

@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
于 2013-10-28T12:48:56.457 回答
3

我需要一个 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
    }
}
于 2017-11-19T22:21:27.120 回答
1

斯威夫特版本的卡洛斯巴博萨回答,如果有人需要它。

使用示例:

myTextField.formatter = CustomTextFieldFormatter(maxLength: 10, isUppercased: true)

class CustomTextFieldFormatter: Formatter {
    var maxLength: UInt
    var isUppercased: Bool
    
    init(maxLength: UInt, isUppercased: Bool) {
        self.maxLength = maxLength
        self.isUppercased = isUppercased
        super.init()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func string(for obj: Any?) -> String? {
        return obj as? String
    }
    
    override func getObjectValue(_ obj: AutoreleasingUnsafeMutablePointer<AnyObject?>?, for string: String, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {
        obj?.pointee = string as AnyObject
        return true
    }
    
    override func isPartialStringValid(_ partialStringPtr: AutoreleasingUnsafeMutablePointer<NSString>, proposedSelectedRange proposedSelRangePtr: NSRangePointer?, originalString origString: String, originalSelectedRange origSelRange: NSRange, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {
        
        if partialStringPtr.pointee.length > maxLength {
            return false
        }
        
        
        if isUppercased && partialStringPtr.pointee != partialStringPtr.pointee.uppercased as NSString {
            partialStringPtr.pointee = partialStringPtr.pointee.uppercased as NSString
            return false
        }
        
        return true
    }
    
    override func attributedString(for obj: Any, withDefaultAttributes attrs: [NSAttributedString.Key : Any]? = nil) -> NSAttributedString? {
        return nil
    }
}
于 2021-04-28T08:01:23.063 回答
-5

Graham Lee 建议的自定义 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;
}
于 2009-05-06T01:03:23.367 回答