How to limit NSTextField text length and keep it always upper case? - objective-c

Need to have an NSTextField with a text limit of 4 characters maximum and show always in upper case but can't figure out a good way of achieving that. I've tried to do it through a binding with a validation method but the validation only gets called when the control loses first responder and that's no good.
Temporarly I made it work by observing the notification NSControlTextDidChangeNotification on the text field and having it call the method:
- (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]];
}
}
But this surely isn't the best way of doing it. Any better suggestion?

I did as Graham Lee suggested and it works fine, here's the custom formatter code:
UPDATED: Added fix reported by Dave Gallagher. Thanks!
#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

Have you tried attaching a custom NSFormatter subclass?

In the above example where I commented, this is bad:
// Don't use:
- (BOOL)isPartialStringValid:(NSString *)partialString
newEditingString:(NSString **)newString
errorDescription:(NSString **)error
{
if ((int)[partialString length] > maxLength)
{
*newString = nil;
return NO;
}
}
Use this (or something like it) instead:
// 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;
}
Both are NSFormatter methods. The first one has an issue. Say you limit text-entry to 10 characters. If you type characters in one-by-one into an NSTextField, it'll work fine and prevent users from going beyond 10 characters.
However, if a user was to paste a string of, say, 25 characters into the Text Field, what'll happen is something like this:
1) User will paste into TextField
2) TextField will accept the string of characters
3) TextField will apply the formatter to the "last" character in the 25-length string
4) Formatter does stuff to the "last" character in the 25-length string, ignoring the rest
5) TextField will end up with 25 characters in it, even though it's limited to 10.
This is because, I believe, the first method only applies to the "very last character" typed into an NSTextField. The second method shown above applies to "all characters" typed into the NSTextField. So it's immune to the "paste" exploit.
I discovered this just now trying to break my application, and am not an expert on NSFormatter, so please correct me if I'm wrong. And very much thanks to you carlosb for posting that example. It helped a LOT! :)

This implementation adopts several of the suggestions commented on above. Notably it works correctly with continuously updating bindings.
In addition:
It implements paste correctly.
It includes some notes on how to use the class effectively in a nib
without further subclassing.
The code:
#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

I needed a Formatter to convert to uppercase for Swift 4. For reference I've included it here:
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
}
}

Swift version of Carlos Barbosa answer, if someone needs it.
Example of usage:
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
}
}

The custom NSFormatter that Graham Lee suggested is the best approach.
A simple kludge would be to set your view controller as the text field's delegate then just block any edit that involves non-uppercase or makes the length longer than 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;
}

Related

Making autocomplete case-insensitive

I'm implementing an autocomplete in cocoa for an OSX application and thus far I've got it all pinned down. The one hangup is that the autocomplete is case-sensitive and that's not really what I want/need. Ideally the autocomplete will be case INSENSITIVE. Relevant code below:
#implementation autocompleteController
- (void)viewDidLoad {
[super viewDidLoad];
self.textField.delegate = self;
}
-(void)controlTextDidChange:(NSNotification *)obj{
NSTextView * fieldEditor = [[obj userInfo] objectForKey:#"NSFieldEditor"];
if (self.isAutocompleting == NO && !self.backspaceKey) {
self.isAutocompleting = YES;
self.lastEntry = [[[fieldEditor string] capitalizedString] copy];
[fieldEditor complete:nil];
self.isAutocompleting = NO;
}
if (self.backspaceKey) {
self.backspaceKey = NO;
}
}
-(NSArray *)control:(NSControl *)control textView:(NSTextView *)textView completions:(NSArray *)words forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index{
NSMutableArray * suggestions = [NSMutableArray array];
NSArray * possibleStrings = #[#"TEST", #"ABC", #"abc", #"amauroy", #"AMA", #"amazing"];
if (!self.lastEntry || !possibleStrings) {
return nil;
}
for (NSString * string in possibleStrings) {
if ([string hasPrefix:self.lastEntry]) {
[suggestions addObject:string];
}
}
return suggestions;
}
-(BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector{
if (commandSelector == #selector(deleteBackward:)) {
self.backspaceKey = YES;
}
return NO;
}
#end
As Pro Blaster points out, the problem is with the following line:
if ([string hasPrefix:self.lastEntry]) {
Your autocompletion is case-sensitive because -hasPrefix: is case-sensitive. One approach is to convert everything to lower case (upper case would also work, of course). Another is to write a case-insensitive version of -hasPrefix: and add it to NSString using a category, like this:
#interface NSString (autocomplete)
- (BOOL)hasPrefixIgnoringCase:(NSString*)aString;
#end;
#implementation NSString (autocomplete)
- (BOOL)hasPrefixIgnoringCase:(NSString*)aString
{
NSRange *prefix = [self rangeOfString:aString options:NSCaseInsensitiveSearch];
return prefix.location == 0 && prefix.length == aString.length;
}
#end
Then use that method in your code:
if ([string hasPrefixIgnoringCase:self.lastEntry]) {
Note: The provided code is untested. The concept is sound, but you may find a syntax error or two.
I did this once.
You would do so by replacing :
for (NSString * string in possibleStrings) {
if ([string hasPrefix:self.lastEntry]) {
[suggestions addObject:string];
}
}
return suggestions
with:
for (NSString * string in possibleStrings) {
if ([[string lowercaseString] hasPrefix:[self.lastEntry lowercaseString]]) {
[suggestions addObject:string];
}
}
return suggestions;

Objective-c Priority Queue

I've started using Objective-c for iOS programming. I switched over from Java, and I wanted to know if there were any existing libraries like the Java Collections Framework for Obj-c, more specifically a priority queue implementation. I've done some searches, but have been unable to come up with anything.
UPDATE: I found this, but would have no idea how to use it myself: http://www.ohloh.net/p/pqlib
I was unable to find an implementation of a priority queue, so I went ahead and made my own. I'm not sure how robust it is, but I hope it might point others in the right direction.
PriorityQueue.h
//
// PriorityQueue.h
//
#import <Foundation/Foundation.h>
#import "comparable.h"
//Implements a priority queue. All objects in queue must implement the comparable protocol and must be all of the same type. The queue can be explicity typed at initialization, otherwise the type of the first object entered will be the type of the queue
#interface PriorityQueue : NSObject{
NSMutableArray *queue;
Class type;
}
- (id)init;
- (id)initWithObjects:(NSSet *)objects;
- (id)initWithCapacity:(int)capacity;
- (id)initWithCapacity:(int)capacity andType:(Class)oType; //Queue will reject objects not of that type
#pragma mark - Useful information
- (BOOL)isEmpty;
- (BOOL)contains:(id<comparable, NSObject>)object;
- (Class)typeOfAllowedObjects; //Returns the type of objects allowed to be stored in the queue
- (int) size;
#pragma mark - Mutation
- (void)clear;
- (BOOL)add:(id<comparable, NSObject>)object;
- (void)remove:(id<comparable, NSObject>)object;
#pragma mark - Getting things out
- (id)peek;
- (id)poll;
- (id)objectMatchingObject:(id<comparable, NSObject>)object;
- (NSArray *)toArray;
#pragma mark -
- (void)print;
#end
PriorityQueue.m
//
// PriorityQueue.m
//
#import "PriorityQueue.h"
#define INITIAL_CAPACITY 50
#implementation PriorityQueue
#pragma mark - Initialization
- (id)init{
return [self initWithCapacity:INITIAL_CAPACITY andType:nil];
}
- (id)initWithObjects:(NSSet *)objects{
self = [self initWithCapacity:INITIAL_CAPACITY andType:nil];
for (id<comparable, NSObject>object in objects){
[self add:object];
}
return self;
}
- (id)initWithCapacity:(int)capacity{
return [self initWithCapacity:capacity andType:nil];
}
- (id)initWithCapacity:(int)capacity andType:(Class)oType{
self = [super init];
if(self){
queue = [[NSMutableArray alloc] init];
type = oType;
}
return self;
}
#pragma mark - Useful information
- (BOOL)isEmpty{
if(queue.count == 0){
return YES;
}
else{ return NO;}
}
- (BOOL)contains:(id<comparable, NSObject>)object{
//Search the array to see if the object is already there
for(id<comparable> o in queue){
if([o isEqual:object]){
return YES;
}
}
return NO;
}
- (Class)typeOfAllowedObjects{
NSLog(#"Allowed Types: %#", type);
return type;
}
- (int) size{
return [queue count];
}
#pragma mark - Mutation
//Mutation
- (void)clear{
[queue removeAllObjects];
}
//A "greater" object (compareTo returns 1) is at the end of the queue.
- (BOOL)add:(id<comparable, NSObject>)object{
//Make sure the object's type is the same as the type of the queue
if(type == nil){
// NSLog(#"Type is nil");
type = [object class];
}
if([object class] != type){
NSLog(#"ERROR: Trying to add incorrect object");
return NO;
}
if([queue count] == 0){
[queue addObject:object];
return YES;
}
for(int i = 0; i < [queue count]; i++){
if([object compareTo:queue[i]] < 0){
[queue insertObject:object atIndex:i];
return YES;
}
}
[queue addObject:object];
return YES;
}
- (void)remove:(id<comparable, NSObject>)object{
[queue removeObject:object];
}
#pragma mark - Getting things out
- (id)peek{
return queue[0];
}
- (id)poll{
//Get the object at the front
id head = queue[0];
//Remove and return that object
[queue removeObject:head];
return head;
}
- (id)objectMatchingObject:(id<comparable, NSObject>)object{
//Search the array to see if the object is already there
for(id<comparable> o in queue){
if([o isEqual:object]){
return o;
}
}
return nil;
}
- (NSArray *)toArray{
return [[NSArray alloc] initWithArray:queue];
}
#pragma mark -
- (NSString *)description{
return [NSString stringWithFormat:#"PriorityQueue: %# allows objects of type %#", queue, type];
}
- (void)print{
NSLog(#"%#", [self description]);
}
#end
Comparable.h
//
// comparable.h
//
#import <Foundation/Foundation.h>
//NOTE: Class must check to make sure it is the same class as whatever is passed in
#protocol comparable
- (int)compareTo:(id<comparable, NSObject>)object;
- (BOOL)isEqual:(id<comparable, NSObject>)object;
#end
See https://mikeash.com/pyblog/using-evil-for-good.html where Mike implements Objective-C wrapper for C++ STD priority queue.
CFBinaryHeap can be used as a Priority Queue and is described as such in the docs: https://developer.apple.com/library/mac/documentation/CoreFoundation/Reference/CFBinaryHeapRef/
The downsides seem to be:
1) There is no remove or update element capability. As far as I can tell you can only remove the min element.
2) It is very C-like and not very pleasant to use in Objc or Swift.
My approach supporting value updates. As CFBinaryHeap does not support updating values I put them on an invalidation list and once being extracted the object is inserted again and a new extraction is made.
/**
Objective-C wrapper around CFBinaryHeap implementing a priority queue and extended by updating a previous value
*/
NS_ASSUME_NONNULL_BEGIN
#interface BEPriorityQueue<ObjectType, ValueType> : NSObject
- (void)dispose;
#property (nonatomic, readonly) NSUInteger count;
- (void)insert:(ObjectType)object value:(ValueType)value;
- (void)update:(ObjectType)object value:(ValueType)value;
/** returns and removes object with lowest value (highest priority */
- (ObjectType)extractMinimum;
- (BOOL)containsObject:(ObjectType)object;
- (id)valueForObject:(id)object;
- (void)removeAllObjects;
#end
NS_ASSUME_NONNULL_END
With this implementation:
NS_ASSUME_NONNULL_BEGIN
#interface BEPriorityQueue()
- (CFComparisonResult)compareObject:(id)object1 with:(id)object2;
#end
static CFComparisonResult BEPriorityQueueCompareItems(const void *ptr1, const void *ptr2, void *info)
{
id object1 = (__bridge id)ptr1;
id object2 = (__bridge id)ptr2;
BEPriorityQueue* queue = (__bridge id)info;
return [queue compareObject:object1 with:object2];
}
static const void *BEPriorityQueueItemRetain(CFAllocatorRef allocator, const void *ptr) {
return CFRetain(ptr);
}
static void BEPriorityQueueItemRelease(CFAllocatorRef allocator, const void *ptr) {
CFRelease(ptr);
}
#implementation BEPriorityQueue
{
BOOL _disposed;
CFBinaryHeapRef _binaryHeapRef;
NSMapTable* _objectToValue;
NSMutableSet* _invalidated;
}
- (instancetype)init
{
self = [super init];
if (self)
{
CFBinaryHeapCallBacks callbacks = (CFBinaryHeapCallBacks) {
.version = 0,
.retain = &BEPriorityQueueItemRetain,
.release = &BEPriorityQueueItemRelease,
.copyDescription = &CFCopyDescription,
.compare = &BEPriorityQueueCompareItems
};
CFBinaryHeapCompareContext compareContext = (CFBinaryHeapCompareContext) {
.version = 0,
.info = (__bridge void *)(self),
.retain = NULL,
.release = NULL,
.copyDescription = NULL,
};
_binaryHeapRef = CFBinaryHeapCreate(NULL, 0, &callbacks, &compareContext);
_objectToValue = [NSMapTable strongToStrongObjectsMapTable];
_invalidated = [NSMutableSet set];
}
return self;
}
- (void)dealloc
{
[self dispose];
if (_binaryHeapRef != NULL)
{
CFRelease(_binaryHeapRef);
_binaryHeapRef = NULL;
}
}
- (void)dispose
{
[self removeAllObjects];
_disposed = YES;
}
#pragma mark internal
- (CFComparisonResult)compareObject:(id)object1 with:(id)object2
{
id value1 = [_objectToValue objectForKey:object1];
id value2 = [_objectToValue objectForKey:object2];
return (CFComparisonResult)[value1 compare:value2];
}
#pragma mark interface
- (NSUInteger)count
{
BEEnsureFalse(_disposed);
return (NSUInteger)CFBinaryHeapGetCount(_binaryHeapRef);
}
- (id)extractMinimum
{
BEEnsureFalse(_disposed);
const void *ptr = NULL;
if (!CFBinaryHeapGetMinimumIfPresent(_binaryHeapRef, &ptr))
return nil;
id object = (__bridge id)ptr;
id value = [_objectToValue objectForKey:object];
CFBinaryHeapRemoveMinimumValue(_binaryHeapRef);
[_objectToValue removeObjectForKey:object];
// if the objects was invalidated, it may no longer be the minimum
// therefore reinsert the object and extract again
if ([_invalidated containsObject:object])
{
[_invalidated removeObject:object];
[self insert:object value:value];
return [self extractMinimum];
}
return object;
}
- (void)insert:(id)object value:(id)value
{
BEEnsureFalse(_disposed);
BEEnsureIsNotNil(object);
BEEnsureIsNotNil(value);
BEEnsureTrue([value respondsToSelector:#selector(compare:)]); // <NSComparable>
[_objectToValue setObject:value forKey:object]; // first to be available furing insertion compare
CFBinaryHeapAddValue(_binaryHeapRef, (__bridge void *)object);
}
- (void)update:(id)object value:(id)value
{
BEEnsureFalse(_disposed);
BEEnsureIsNotNil(object);
BEEnsureTrue([value respondsToSelector:#selector(compare:)]); // <NSComparable>
[_objectToValue setObject:value forKey:object]; // first to be available during insertion compare
[_invalidated addObject:object];
}
- (BOOL)containsObject:(id)object
{
BEEnsureFalse(_disposed);
return CFBinaryHeapContainsValue(_binaryHeapRef, (__bridge void *)object);
}
- (id)valueForObject:(id)object
{
return [_objectToValue objectForKey:object];
}
- (void)removeAllObjects
{
CFBinaryHeapRemoveAllValues(_binaryHeapRef);
[_objectToValue removeAllObjects];
[_invalidated removeAllObjects];
}
#end
NS_ASSUME_NONNULL_END

Allow only alpha numeric characters in UITextView

Is there anyway i can allow user to enter only alpha numeric characters in a text view and no other character.
EDIT:
Tried,
if ([_txtView.text rangeOfCharacterFromSet:alphaSet].location != NSNotFound)
{
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:#"Hello" message:#"Only alpha numeric characters are allowed" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
return;
}
but this only works for some of the times
Thanks!!
You can achieve that using [[[NSCharacterSet alphanumericCharacterSet] invertedSet]. This method will return a character set containing only characters that don’t exist in the receiver.
NSCharacterSet *charactersToBlock = [[NSCharacterSet alphanumericCharacterSet] invertedSet];
//Conform UITextField delegate and implement this method.
- (BOOL)textField:(UITextField *)field shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)characters
{
return ([characters rangeOfCharacterFromSet:charactersToBlock].location == NSNotFound);
}
Try this:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if (textField == txtWebsite) {
NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:#"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 "];
if ([string rangeOfCharacterFromSet:set].location != NSNotFound) {
return YES;
}
else {
return NO;
}
}
else {
return YES;
}
}
write code in delegate method of uitextfield.
set delegate for textview and override/implement test should change in range method
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
NSCharacterSet *alphaSet = [NSCharacterSet alphanumericCharacterSet];
BOOL valid = [[text stringByTrimmingCharactersInSet:alphaSet] isEqualToString:#""];
return valid;
}
Equivalent Swift 3 version of the answer provided by #user1113101
Though it's late to answer and there are other simple and great approaches, but this answer might be useful to someone.
This is simple and worked like a charm for me.
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
/// 1. replacementText is NOT empty means we are entering text or pasting text: perform the logic
/// 2. replacementText is empty means we are deleting text: return true
if text.characters.count > 0 {
var allowedCharacters = CharacterSet.alphanumerics
let unwantedStr = text.trimmingCharacters(in: allowedCharacters)
return unwantedStr.characters.count == 0
}
return true
}
Note: This will work for pasting strings into the text field as well. Pasted string will not be displayed in text field if it contains any unwanted characters.
// Add this in ViewDidLoad or any init method
NSCharacterSet *blockedCharacters = [[[NSCharacterSet alphanumericCharacterSet] invertedSet] retain];
then Set your textfield's delegate in nib file .
- (BOOL)textField:(UITextField *)field shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)characters
{
return ([characters rangeOfCharacterFromSet:blockedCharacters].location == NSNotFound);
}
Or there is another way in shouldChangeCharactersInRange method. You can check
{
NSString *stringPlace = #"[a-z A-Z]*";
NSPredicate *testPlace = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", stringPlace];
BOOL matches = [testPlace evaluateWithObject:string];
if (!matches && string.length > 5)
{
return NO;
}
return YES;
}

How can I enable case insensitive autocomplete for a NSComboBox?

I've bound the NSComboBox bounded to a data source within interface builder.
I correctly get the autocomplete suggestions, when I type something in the NSComboBox.
However the autocomplete is case sensitive, which means I don't get suggestion if the character uses the wrong case.
How can I enable case insensitive autocomplete for a NSComboBox, which is bound to the data source in interface builder ?
Thanks
You should implement comboBox:completedString: in your NSComboBox Data Source, e.g:
- (NSString *)comboBox:(NSComboBox *)comboBox completedString:(NSString *)partialString
{
for (NSString dataString in dataSourceArray) {
if ([[dataString commonPrefixWithString:partialString options:NSCaseInsensitiveSearch] length] == [commonPrefixWithString:partialString length]) {
return testItem;
}
}
return #"";
}
you can subclassing an NSComboBoxCell and overriding [NSComboBoxCell completedString:].
- (NSString *)completedString:(NSString *)string
{
NSString *result = nil;
if (string == nil)
return result;
for (NSString *item in self.objectValues) {
NSString *truncatedString = [item substringToIndex:MIN(item.length, string.length)];
if ([truncatedString caseInsensitiveCompare:string] == NSOrderedSame) {
result = item;
break;
}
}
return result;
}

Check that a input to UITextField is numeric only

How do I validate the string input to a UITextField? I want to check that the string is numeric, including decimal points.
You can do it in a few lines like this:
BOOL valid;
NSCharacterSet *alphaNums = [NSCharacterSet decimalDigitCharacterSet];
NSCharacterSet *inStringSet = [NSCharacterSet characterSetWithCharactersInString:myInputField.text];
valid = [alphaNums isSupersetOfSet:inStringSet];
if (!valid) // Not numeric
-- this is for validating input is numeric chars only. Look at the documentation for NSCharacterSet for the other options. You can use characterSetWithCharactersInString to specify any set of valid input characters.
There are a few ways you could do this:
Use NSNumberFormatter's numberFromString: method. This will return an NSNumber if it can parse the string correctly, or nil if it cannot.
Use NSScanner
Strip any non-numeric character and see if the string still matches
Use a regular expression
IMO, using something like -[NSString doubleValue] wouldn't be the best option because both #"0.0" and #"abc" will have a doubleValue of 0. The *value methods all return 0 if they're not able to convert the string properly, so it would be difficult to distinguish between a legitimate string of #"0" and a non-valid string. Something like C's strtol function would have the same issue.
I think using NSNumberFormatter would be the best option, since it takes locale into account (ie, the number #"1,23" in Europe, versus #"1.23" in the USA).
I use this code in my Mac app, the same or similar should work with the iPhone. It's based on the RegexKitLite regular expressions and turns the text red when its invalid.
static bool TextIsValidValue( NSString* newText, double &value )
{
bool result = false;
if ( [newText isMatchedByRegex:#"^(?:|0|[1-9]\\d*)(?:\\.\\d*)?$"] ) {
result = true;
value = [newText doubleValue];
}
return result;
}
- (IBAction) doTextChanged:(id)sender;
{
double value;
if ( TextIsValidValue( [i_pause stringValue], value ) ) {
[i_pause setTextColor:[NSColor blackColor]];
// do something with the value
} else {
[i_pause setTextColor:[NSColor redColor]];
}
}
If you want a user to only be allowed to enter numerals, you can make your ViewController implement part of UITextFieldDelegate and define this method:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSString *resultingString = [textField.text stringByReplacingCharactersInRange: range withString: string];
// The user deleting all input is perfectly acceptable.
if ([resultingString length] == 0) {
return true;
}
NSInteger holder;
NSScanner *scan = [NSScanner scannerWithString: resultingString];
return [scan scanInteger: &holder] && [scan isAtEnd];
}
There are probably more efficient ways, but I find this a pretty convenient way. And the method should be readily adaptable to validating doubles or whatever: just use scanDouble: or similar.
#pragma mark - UItextfield Delegate
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if ([string isEqualToString:#"("]||[string isEqualToString:#")"]) {
return TRUE;
}
NSLog(#"Range ==%d ,%d",range.length,range.location);
//NSRange *CURRANGE = [NSString rangeOfString:string];
if (range.location == 0 && range.length == 0) {
if ([string isEqualToString:#"+"]) {
return TRUE;
}
}
return [self isNumeric:string];
}
-(BOOL)isNumeric:(NSString*)inputString{
BOOL isValid = NO;
NSCharacterSet *alphaNumbersSet = [NSCharacterSet decimalDigitCharacterSet];
NSCharacterSet *stringSet = [NSCharacterSet characterSetWithCharactersInString:inputString];
isValid = [alphaNumbersSet isSupersetOfSet:stringSet];
return isValid;
}
Here are a few one-liners which combine Peter Lewis' answer above (Check that a input to UITextField is numeric only) with NSPredicates
#define REGEX_FOR_NUMBERS #"^([+-]?)(?:|0|[1-9]\\d*)(?:\\.\\d*)?$"
#define REGEX_FOR_INTEGERS #"^([+-]?)(?:|0|[1-9]\\d*)?$"
#define IS_A_NUMBER(string) [[NSPredicate predicateWithFormat:#"SELF MATCHES %#", REGEX_FOR_NUMBERS] evaluateWithObject:string]
#define IS_AN_INTEGER(string) [[NSPredicate predicateWithFormat:#"SELF MATCHES %#", REGEX_FOR_INTEGERS] evaluateWithObject:string]
For integer test it'll be:
- (BOOL) isIntegerNumber: (NSString*)input
{
return [input integerValue] != 0 || [input isEqualToString:#"0"];
}
You can use the doubleValue of your string like
NSString *string=#"1.22";
double a=[string doubleValue];
i think this will return a as 0.0 if the string is invalid (it might throw an exception, in which case you can just catch it, the docs say 0.0 tho). more info here
Hi had the exact same problem and I don't see the answer I used posted, so here it is.
I created and connected my text field via IB. When I connected it to my code via Control+Drag, I chose Action, then selected the Editing Changed event. This triggers the method on each character entry. You can use a different event to suit.
Afterwards, I used this simple code to replace the text. Note that I created my own character set to include the decimal/period character and numbers. Basically separates the string on the invalid characters, then rejoins them with empty string.
- (IBAction)myTextFieldEditingChangedMethod:(UITextField *)sender {
NSCharacterSet *validCharacterSet = [NSCharacterSet characterSetWithCharactersInString:#".0123456789"];
NSCharacterSet *invalidCharacterSet = validCharacterSet.invertedSet;
sender.text = [[sender.text componentsSeparatedByCharactersInSet:invalidCharacterSet] componentsJoinedByString:#""];
}
Credits:
Remove all but numbers from NSString
Late to the game but here a handy little category I use that accounts for decimal places and the local symbol used for it. link to its gist here
#interface NSString (Extension)
- (BOOL) isAnEmail;
- (BOOL) isNumeric;
#end
#implementation NSString (Extension)
/**
* Determines if the current string is a valid email address.
*
* #return BOOL - True if the string is a valid email address.
*/
- (BOOL) isAnEmail
{
NSString *emailRegex = #"[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}";
NSPredicate *emailTest = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", emailRegex];
return [emailTest evaluateWithObject:self];
}
/**
* Determines if the current NSString is numeric or not. It also accounts for the localised (Germany for example use "," instead of ".") decimal point and includes these as a valid number.
*
* #return BOOL - True if the string is numeric.
*/
- (BOOL) isNumeric
{
NSString *localDecimalSymbol = [[NSLocale currentLocale] objectForKey:NSLocaleDecimalSeparator];
NSMutableCharacterSet *decimalCharacterSet = [NSMutableCharacterSet characterSetWithCharactersInString:localDecimalSymbol];
[decimalCharacterSet formUnionWithCharacterSet:[NSCharacterSet alphanumericCharacterSet]];
NSCharacterSet* nonNumbers = [decimalCharacterSet invertedSet];
NSRange r = [self rangeOfCharacterFromSet: nonNumbers];
if (r.location == NSNotFound)
{
// check to see how many times the decimal symbol appears in the string. It should only appear once for the number to be numeric.
int numberOfOccurances = [[self componentsSeparatedByString:localDecimalSymbol] count]-1;
return (numberOfOccurances > 1) ? NO : YES;
}
else return NO;
}
#end
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if(string.length > 0)
{
NSCharacterSet *numbersOnly = [NSCharacterSet characterSetWithCharactersInString:#"0123456789"];
NSCharacterSet *characterSetFromTextField = [NSCharacterSet characterSetWithCharactersInString:string];
BOOL stringIsValid = [numbersOnly isSupersetOfSet:characterSetFromTextField];
return stringIsValid;
}
return YES;
}
IMO the best way to accomplish your goal is to display a numeric keyboard rather than the normal keyboard. This restricts which keys are available to the user. This alleviates the need to do validation, and more importantly it prevents the user from making a mistake. The number pad is also much nicer for entering numbers because the keys are substantially larger.
In interface builder select the UITextField, go to the Attributes Inspector and change the "Keyboard Type" to "Decimal Pad".
That'll make the keyboard look like this:
The only thing left to do is ensure the user doesn't enter in two decimal places. You can do this while they're editing. Add the following code to your view controller. This code removes a second decimal place as soon as it is entered. It appears to the user as if the 2nd decimal never appeared in the first place.
- (void)viewDidLoad
{
[super viewDidLoad];
[self.textField addTarget:self
action:#selector(textFieldDidChange:)
forControlEvents:UIControlEventEditingChanged];
}
- (void)textFieldDidChange:(UITextField *)textField
{
NSString *text = textField.text;
NSRange range = [text rangeOfString:#"."];
if (range.location != NSNotFound &&
[text hasSuffix:#"."] &&
range.location != (text.length - 1))
{
// There's more than one decimal
textField.text = [text substringToIndex:text.length - 1];
}
}
#property (strong) NSNumberFormatter *numberFormatter;
#property (strong) NSString *oldStringValue;
- (void)awakeFromNib
{
[super awakeFromNib];
self.numberFormatter = [[NSNumberFormatter alloc] init];
self.oldStringValue = self.stringValue;
[self setDelegate:self];
}
- (void)controlTextDidChange:(NSNotification *)obj
{
NSNumber *number = [self.numberFormatter numberFromString:self.stringValue];
if (number) {
self.oldStringValue = self.stringValue;
} else {
self.stringValue = self.oldStringValue;
}
}
Old thread, but it's worth mentioning that Apple introduced NSRegularExpression in iOS 4.0. (Taking the regular expression from Peter's response)
// Look for 0-n digits from start to finish
NSRegularExpression *noFunnyStuff = [NSRegularExpression regularExpressionWithPattern:#"^(?:|0|[1-9]\\d*)(?:\\.\\d*)?$" options:0 error:nil];
// There should be just one match
if ([noFunnyStuff numberOfMatchesInString:<#theString#> options:0 range:NSMakeRange(0, <#theString#>.length)] == 1)
{
// Yay, digits!
}
I suggest storing the NSRegularExpression instance somewhere.
I wanted a text field that only allowed integers. Here's what I ended up with (using info from here and elsewhere):
Create integer number formatter (in UIApplicationDelegate so it can be reused):
#property (nonatomic, retain) NSNumberFormatter *integerNumberFormatter;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Create and configure an NSNumberFormatter for integers
integerNumberFormatter = [[NSNumberFormatter alloc] init];
[integerNumberFormatter setMaximumFractionDigits:0];
return YES;
}
Use filter in UITextFieldDelegate:
#interface MyTableViewController : UITableViewController <UITextFieldDelegate> {
ictAppDelegate *appDelegate;
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
// Make sure the proposed string is a number
NSNumberFormatter *inf = [appDelegate integerNumberFormatter];
NSString* proposedString = [textField.text stringByReplacingCharactersInRange:range withString:string];
NSNumber *proposedNumber = [inf numberFromString:proposedString];
if (proposedNumber) {
// Make sure the proposed number is an integer
NSString *integerString = [inf stringFromNumber:proposedNumber];
if ([integerString isEqualToString:proposedString]) {
// proposed string is an integer
return YES;
}
}
// Warn the user we're rejecting the change
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate);
return NO;
}
Not so elegant, but simple :)
- (BOOL) isNumber: (NSString*)input
{
return [input doubleValue] != 0 || [input isEqualToString:#"0"] || [input isEqualToString:#"0.0"];
}
Accept decimal values in text fields with single (.)dot working with iPad and iPhone in Swift 3
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let inverseSet = NSCharacterSet(charactersIn:"0123456789").inverted
let components = string.components(separatedBy: inverseSet)
let filtered = components.joined(separator: "")
if filtered == string {
return true
} else {
if string == "." {
let countdots = textField.text!.components(separatedBy:".").count - 1
if countdots == 0 {
return true
}else{
if countdots > 0 && string == "." {
return false
} else {
return true
}
}
}else{
return false
}
}
}
To be more international (and not only US colored ;-) ) just replace in the code above by
-(NSNumber *) getNumber
{
NSString* localeIdentifier = [[NSLocale autoupdatingCurrentLocale] localeIdentifier];
NSLocale *l_en = [[NSLocale alloc] initWithLocaleIdentifier: localeIdentifier] ;
return [self getNumberWithLocale: [l_en autorelease] ];
}
This answer uses NSFormatter as said previously. Check it out:
#interface NSString (NSNumber)
- (BOOL) isNumberWithLocale:(NSLocale *) stringLocale;
- (BOOL) isNumber;
- (NSNumber *) getNumber;
- (NSNumber *) getNumberWithLocale:(NSLocale*) stringLocale;
#end
#implementation NSString (NSNumber)
- (BOOL) isNumberWithLocale:(NSLocale *) stringLocale
{
return [self getNumberWithLocale:stringLocale] != nil;
}
- (BOOL) isNumber
{
return [ self getNumber ] != nil;
}
- (NSNumber *) getNumber
{
NSLocale *l_en = [[NSLocale alloc] initWithLocaleIdentifier: #"en_US"] ;
return [self getNumberWithLocale: [l_en autorelease] ];
}
- (NSNumber *) getNumberWithLocale:(NSLocale*) stringLocale
{
NSNumberFormatter *formatter = [[ [ NSNumberFormatter alloc ] init ] autorelease];
[formatter setLocale: stringLocale ];
return [ formatter numberFromString:self ];
}
#end
I hope it helps someone. =)
#import "NSString+Extension.h"
//#interface NSString (Extension)
//
//- (BOOL) isAnEmail;
//- (BOOL) isNumeric;
//
//#end
#implementation NSString (Extension)
- (BOOL) isNumeric
{
NSString *emailRegex = #"[0-9]+";
NSPredicate *emailTest = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", emailRegex];
return [emailTest evaluateWithObject:self];
// NSString *localDecimalSymbol = [[NSLocale currentLocale] objectForKey:NSLocaleDecimalSeparator];
// NSMutableCharacterSet *decimalCharacterSet = [NSMutableCharacterSet characterSetWithCharactersInString:localDecimalSymbol];
// [decimalCharacterSet formUnionWithCharacterSet:[NSCharacterSet alphanumericCharacterSet]];
//
// NSCharacterSet* nonNumbers = [decimalCharacterSet invertedSet];
// NSRange r = [self rangeOfCharacterFromSet: nonNumbers];
//
// if (r.location == NSNotFound)
// {
// // check to see how many times the decimal symbol appears in the string. It should only appear once for the number to be numeric.
// int numberOfOccurances = [[self componentsSeparatedByString:localDecimalSymbol] count]-1;
// return (numberOfOccurances > 1) ? NO : YES;
// }
// else return NO;
}
In Swift 4:
let formatString = "12345"
if let number = Decimal(string:formatString){
print("String contains only number")
}
else{
print("String doesn't contains only number")
}
This covers: Decimal part control (including number of decimals allowed), copy/paste control, international separators.
Steps:
Make sure your view controller inherits from UITextFieldDelegate
class MyViewController: UIViewController, UITextFieldDelegate {...
In viewDidLoad, set your control delegate to self:
override func viewDidLoad() {
super.viewDidLoad();
yourTextField.delegate = self
}
Implement the following method and update the "decsAllowed" constant with the desired amount of decimals or 0 if you want a natural number.
Swift 4
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let decsAllowed: Int = 2
let candidateText = NSString(string: textField.text!).replacingCharacters(in: range, with: string)
let decSeparator: String = NumberFormatter().decimalSeparator!;
let splitted = candidateText.components(separatedBy: decSeparator)
let decSeparatorsFound = splitted.count - 1
let decimalPart = decSeparatorsFound > 0 ? splitted.last! : ""
let decimalPartCount = decimalPart.characters.count
let characterSet = NSMutableCharacterSet.decimalDigit()
if decsAllowed > 0 {characterSet.addCharacters(in: decSeparator)}
let valid = characterSet.isSuperset(of: CharacterSet(charactersIn: candidateText)) &&
decSeparatorsFound <= 1 &&
decsAllowed >= decimalPartCount
return valid
}
If afterwards you need to safely convert that string into a number, you can just use Double(yourstring) or Int(yourstring) type cast, or the more academic way:
let formatter = NumberFormatter()
let theNumber: NSNumber = formatter.number(from: yourTextField.text)!