需要一个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]]; } }
但这肯定不是最好的方法.还有更好的建议吗?
我按照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
您是否尝试过附加自定义NSFormatter
子类?
在我评论的上述示例中,这很糟糕:
// 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长度字符串中的"last"字符
4)Formatter对25长度字符串中的"last"字符进行填充,忽略其余字符
5)TextField最终将包含25个字符,即使它限制为10个字符.
这是因为,我认为,第一种方法仅适用于键入NSTextField的"最后一个字符".上面显示的第二种方法适用于键入NSTextField的"所有字符".所以它不受"粘贴"攻击的影响.
我刚刚发现这个试图破坏我的应用程序,并且不是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