【问题标题】:How to limit NSTextField text length and keep it always upper case?如何限制 NSTextField 文本长度并始终保持大写?
【发布时间】:2009-05-05 21:39:51
【问题描述】:

需要有一个文本限制为最多 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]];
  }
}

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

【问题讨论】:

    标签: objective-c cocoa user-interface nstextfield


    【解决方案1】:

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

    【讨论】:

    • 您应该接受格雷厄姆的回答,因为他为您指明了正确的方向!干得好!
    • 感谢您抽出宝贵时间回来发布整个解决方案!
    • 我发现上面的代码有一个错误。有一个使用 isPartialStringValid:newEditingString:errorDescription: 的潜在漏洞。如果您在 NSTextField 中逐个字符地在键盘上输入文本,则不会出现任何问题。但是,如果您将 2 个或更多字符的字符串粘贴到文本字段中,它将对输入的最后一个字符执行验证,但忽略所有以前输入的字符。这可能会导致在文本字段中插入的字符超出允许范围。下面我将发布更多细节和解决方案(此处空间不足)。
    • 您能否将“-init {”编辑为“-(id)init{”?将使复制/粘贴更容易。
    • 请记住,如果您将自定义格式化程序从 Interface Builder 拖到文本字段上,您将需要使用 - (id)initWithCoder:(NSCoder *)aDecoder 而不是 - (id)init 来初始化 ivars比如maxLength
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

      // 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) 格式化程序对 25 长字符串中的“最后一个”字符进行处理,忽略其余部分

      5) TextField 最终会包含 25 个字符,即使它被限制为 10 个。

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

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

      【讨论】:

      • 用户甚至不需要粘贴。用户定义的自定义键绑定(详见hcs.harvard.edu/~jrus/site/cocoa-text.html)可以插入任何字符串,在基本多语言平面之外的单个代码点将是 Cocoa 的两字节 (UTF-16) 意义上的多个“字符”。
      • 感谢彼得的精彩文章!
      【解决方案4】:

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

      另外:

      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
      

      【讨论】:

      • 有人可以快速提供解决方案吗?我不知道如何转换这些行,尤其是proposedSelRangePtr->location = origSelRange.location + insertLength;
      【解决方案5】:

      我需要一个 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
          }
      }
      

      【讨论】:

        【解决方案6】:

        Carlos Barbosa 的 Swift 版本回答,如果有人需要的话。

        使用示例:

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

        【讨论】:

          【解决方案7】:

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

          【讨论】:

          • 只存在于 iOS 中的 UITextField。他正在使用 NSTextField 并且此方法不存在。
          猜你喜欢
          • 2018-02-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-09-13
          • 1970-01-01
          • 1970-01-01
          • 2018-07-05
          相关资源
          最近更新 更多