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

问题描述:

需要具有最多4个字符的文本限制的NSTextField,并且始终以大写显示,但无法找出实现该目的的好方法。我试图通过绑定与验证方法来做到这一点,但验证只有在控件失去第一响应者时才会被调用,这是不好的。如何限制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]]; 
    } 
} 

但是,这肯定是不这样做的最佳方式。有什么更好的建议?

我一样格雷厄姆·李建议它工作正常,这里是自定义的格式化程序代码:

更新:添加了由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 
+1

你应该接受Grahams的回答,因为他指出你的方向正确!好工作寿! – Jab

+1

感谢您花时间回来发布整个解决方案! –

+1

我发现上面的代码有错误。有一个潜在的利用isPartialStringValid:newEditingString:errorDescription :.如果您在键盘上逐字输入文本到NSTextField中,则不会出现问题。但是,如果您将一个包含2个或更多字符的字符串粘贴到文本字段中,它将对输入的最后一个字符执行验证,但忽略所有先前输入的字符。这可能会导致插入更多字符而不允许插入到文本字段中。 下面我会发布更多的细节和解决方案(空间不足)。 –

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

格雷姆李建议的自定义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; 
} 
+6

,只有在iOS的存在与的UITextField。他正在使用NSTextField,并且此方法不存在。 –

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

// 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)用户将其粘贴到文本字段

2)的TextField将接受的字符的字符串

3)的TextField将格式化应用到“最后”字符在25长度的字符串

4)格式化程序的东西“最后“字符在25长度的字符串,忽略其它

5)文本字段将结束它25个字符,即使它限制为10

这是因为,我相信,只有第一种方法适用于键入NSTextField的“最后一个字符”。上面显示的第二种方法适用于键入NSTextField的“所有字符”。所以它不受“粘贴”漏洞的影响。

我刚刚发现了这个问题,试图破坏我的应用程序,我不是NSFormatter的专家,所以如果我错了,请纠正我。非常感谢你carlosb张贴该示例。它帮助了很多! :)

+5

用户甚至不需要粘贴。用户定义的自定义键绑定(请参阅http://www.hcs.harvard.edu/~jrus/site/cocoa-text.html以获取详细信息)可以插入任何字符串以及基本多语言以外的单个代码点平面在Cocoa的两字节(UTF-16)意义上将是多个“字符”。 –

+0

感谢那篇精彩的文章彼得! –

该实现采用了上述评论的几个建议。值得注意的是,它可以正确地连续更新绑定。

另外:

  1. 它正确地实现糊。

  2. 它包括一些笔记,关于如何在没有进一步子类化的情况下有效地在笔尖 中使用该类。

代码:

@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

值得赞赏 –

我需要一个格式转换为大写的斯威夫特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 
    } 
}