Objective c 如何限制NSTextField文本长度并始终保持大写?

Objective c 如何限制NSTextField文本长度并始终保持大写?,objective-c,cocoa,user-interface,nstextfield,Objective C,Cocoa,User Interface,Nstextfield,需要一个文本限制为最多4个字符的NSTextField,并始终以大写形式显示,但无法找到一个实现此目标的好方法。我曾尝试通过绑定验证方法来实现这一点,但只有当控件失去第一响应程序时才会调用验证,这是不好的 暂时,我通过观察文本字段上的通知NSControlTextDidChangeNotification并让它调用以下方法使其工作: - (void)textDidChange:(NSNotification*)notification { NSTextField* textField = [

需要一个文本限制为最多4个字符的NSTextField,并始终以大写形式显示,但无法找到一个实现此目标的好方法。我曾尝试通过绑定验证方法来实现这一点,但只有当控件失去第一响应程序时才会调用验证,这是不好的

暂时,我通过观察文本字段上的通知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]];
  }
}

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

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

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

我按照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

在上面我评论的例子中,这是不好的:

// Don't use:
- (BOOL)isPartialStringValid:(NSString *)partialString
            newEditingString:(NSString **)newString
            errorDescription:(NSString **)error
{
    if ((int)[partialString length] > maxLength)
    {
        *newString = nil;
        return NO;
    }
}
改用这个(或类似的东西):

这两种方法都是NSFormatter方法。第一个问题。假设您将文本输入限制为10个字符。如果在NSTEXT字段中逐个键入字符,它将正常工作,并防止用户超过10个字符

但是,如果用户将一个字符串粘贴到文本字段中,例如25个字符,则会发生如下情况:

1) 用户将粘贴到文本字段中

2) TextField将接受字符串

3) TextField将格式化程序应用于25长度字符串中的“最后一个”字符

4) 格式化程序将填充到25长度字符串中的“最后一个”字符,忽略其余字符

5) TextField将以25个字符结束,即使限制为10个字符

这是因为,我相信,第一种方法只适用于在NSTextField中键入的“最后一个字符”。上面显示的第二种方法适用于在NSTEXT字段中键入的“所有字符”。所以它对“粘贴”攻击免疫


我刚刚在尝试破解我的应用程序时发现了这个问题,我不是NSFormatter方面的专家,所以如果我错了,请纠正我。非常感谢您carlosb发布了这个示例。这帮了大忙!:)

这一实施采纳了上述几条建议。值得注意的是,它可以正确地处理不断更新的绑定

此外:

  • 它正确地实现了粘贴

  • 它包括一些关于如何在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
    

    我需要一个格式化程序将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
        }
    }
    
    <代码>导入基础 类UppercaseFormatter:格式化程序{ 覆盖func字符串(对象:有?->字符串{ 如果let stringValue=obj as?字符串{ 返回stringValue.uppercased() } 归零 } 重写func getObjectValue(j:autoreleasingusafmtablepointer?,对于字符串:string,errorDescription error:autoreleasingusafmtablepointer?)->Bool{ obj?.pointee=字符串作为任意对象 返回真值 } } 如果有人需要,快速回答

    用法示例:

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

    class CustomTextFieldFormatter:格式化程序{
    变量最大长度:UInt
    变量大写:Bool
    init(最大长度:UInt,大写:Bool){
    self.maxLength=maxLength
    self.isUppercased=isUppercased
    super.init()
    }
    必需初始化?(编码器:NSCoder){
    fatalError(“初始化(编码者:)尚未实现”)
    }
    覆盖func字符串(对象:有?->字符串{
    返回obj作为字符串
    }
    重写func getObjectValue(j:autoreleasingusafmtablepointer?,对于字符串:string,errorDescription error:autoreleasingusafmtablepointer?)->Bool{
    obj?.pointee=字符串作为任意对象
    返回真值
    }
    重写函数isPartialStringValid(uPartialStringPtr:AutoreleasingUnsafeMutablePointer,proposedSelectedRange proposedSelRangePtr:NSRangePointer?,OriginalStringOrigString:String,originalSelectedRange origSelRange:NSRange,错误描述错误:AutoreleasingUnsafeMutablePointer?->Bool{
    如果partialStringPtr.pointee.length>maxLength{
    返回错误
    }
    如果isUppercased&&partialStringPtr.pointee!=partialStringPtr.pointee.uppercased为NSString{
    partialStringPtr.pointee=partialStringPtr.pointee.UPPERCASE为NSString
    返回错误
    }
    返回真值
    }
    重写func attributedString(对于obj:Any,使用defaultattributes attrs:[NSAttributedString.Key:Any]?=nil)->NSAttributedString{
    归零
    }
    }
    
    你应该接受格雷厄姆的回答,因为他为你指明了正确的方向!干得好!感谢您抽出时间回来发布整个解决方案!我发现上面的代码有一个错误。使用isPartialStringValid:newEditingString:errorDescription:存在潜在的攻击。如果在键盘上逐个字符地将文本输入NSTEXT字段,则不会出现任何问题。但是,如果将包含2个或更多字符的字符串粘贴到文本字段中,它将对最后输入的字符执行验证,但忽略以前输入的所有字符。这可能导致在文本字段中插入的字符超过允许的数量。下面我将发布更多详细信息和解决方案(此处空间不足)。请您编辑“-init{”以读取“-(id)init{”?将使复制/粘贴更容易。请记住,如果您从Interface Builder将自定义格式设置程序拖到文本字段上,您将需要使用-(id)initWithCoder:(NSCoder*)aDecoder而不是
    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
        }
    }