How can I check the value of a string obtained via scripting? - objective-c

My Mac app gets 2 string values from another app via scripting. Under certain conditions, the sender supplies "0-1". I need to detect this and blank the text box that displays it. The following, which only shows code for the second string, works in the debugger, but not when run outside it.
- (void)controlTextDidChange:(NSNotification *)notification
{
// there was a change in a text control
int tmpInt2 = 0;
NSMutableString *tmp2 = [NSMutableString stringWithString:[inputTextField2 stringValue]];
//NSLog(#"text box changed. value: %i", val);
if ([tmp2 length] > 3)
{
tmp2 = [NSMutableString stringWithString:[tmp2 substringToIndex:[tmp2 length] - 1]];
[inputTextField2 setStringValue:tmp2];
}
if ([tmp2 length] == 3)
{
tmpInt2 = [tmp2 intValue];
if (tmpInt2 > 360 || tmpInt2 < 0 || [tmp2 isEqualToString:#"0-1"])
{
//[self showAlert:#"Heading must be between 000 and 360"];
[inputTextField2 setStringValue:#""];
//[inputTextField2 setBackgroundColor:[NSColor yellowColor]];
[tmp2 setString:#""];
}
}
if ([[inputTextField2 stringValue] rangeOfCharacterFromSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet]].location != NSNotFound)
{
NSLog(#"This is not a positive integer");
//NSMutableString *strippedString = [NSMutableString stringWithCapacity:tmp.length];
[inputTextField2 setStringValue:#""];
//[[inputTextField2 cell] setBackgroundColor:[NSColor yellowColor]];
[tmp2 setString:#""];
}
/*
if ([tmp2 isEqualToString:#"0-1"])
{
[inputTextField2 setStringValue:#""];
[tmp2 setString:#""];
}
*/
if ([tmp2 rangeOfString:#"-"].location == NSNotFound) {
NSLog(#"string does not contain 0-1");
} else {
NSLog(#"string contains 0-1!");
[inputTextField2 setStringValue:#""];
[tmp2 setString:#""];
}
}

You should look into #trojanfoe's suggestion of using NSFormatter or one of its pre-defined subclasses. However you appear to misunderstand the purpose of NSMutableString, so I offer the following version of your code with some comments embedded. The text field used for the test was given a placeholder value of "Enter Heading", and it is assumed ARC is enabled. Modern property access syntax is used (object.property). HTH.
- (void)controlTextDidChange:(NSNotification *)notification
{
// there was a change in a text control
NSTextField *inputTextField = notification.object; // get the field
NSTextFieldCell *fieldCell = inputTextField.cell; // and its cell - we use the placeholder text for feedback in this sample
fieldCell.placeholderString = #"Enter heading"; // user has typed, restore default message
NSString *contents = inputTextField.stringValue; // an NSMutableString is not required, you never mutate this string
NSUInteger length = contents.length;
if (length > 3)
{
// remove last character - did you mean to truncate to three characters?
inputTextField.stringValue = [contents substringToIndex:length - 1];
}
else if (length == 3)
{
int tmpInt = contents.intValue;
if (tmpInt > 360 || tmpInt < 0 || [contents isEqualToString:#"0-1"])
{
fieldCell.placeholderString = #"Heading must be between 000 and 360"; // inform user why field was blanked
inputTextField.stringValue = #"";
}
}
else if ([contents rangeOfCharacterFromSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet]].location != NSNotFound)
{
// you might want different logic here
// if a user types "12Y" you delete everything, deleting just the "Y" might be more friendly
// ("Y" picked as an example as it could be a miss hit for the 6 or 7 keys)
fieldCell.placeholderString = #"Enter a positive integer"; // inform user why field was blanked
inputTextField.stringValue = #"";
}
}
Addendum - Comment Followup
Exactly what inputs you are expecting and what you wish to do with them is unclear. The first if just removes the last character from strings longer than 3 without doing any other checks. However I may have misinterpreted your intentions here, you have have intended to continue processing after that first if, e.g. something like:
...
if (length > 3)
{
// remove last character - did you mean to truncate to three characters?
contents = [contents substringToIndex:length - 1];
length -= 1;
}
if (length == 3)
{
...
Which means if your input is longer than 3 characters you remove the last (did you not want to simply truncate to 3? If so just change those two lines of code to do so) and then you continue with the following if/else.

Related

In my macOS application, I am working with UserDefaults dictionaryRepresentation. Sometimes I get strings with unknown encoding. Any suggesition?

I am working with a Objective-C Application, specifically I am gathering the dictionary representation of NSUserDefaults with this code:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *userDefaultsDict = [defaults dictionaryRepresentation];
While enumerating keys and objects of the resulting dict, sometimes I find a kind of opaque string that you can see in the following picture:
So it seems like an encoding problem.
If I try to print description of the string, the debugger correctly prints:
Printing description of obj:
tsuqsx
However, if I try to write obj to a file, or use it in any other way, I get an unreadable output like this:
What I would like to achieve is the following:
Detect in some way that the string has the encoding problem.
Convert the string to UTF8 encoding to use it in the rest of the program.
Any help is greatly appreciated. Thanks
EDIT: Very Hacky possible Solution that helps explaining what I am trying to do.
After trying all possible solutions based on dataUsingEncoding and back, I ended up with the following solution, absolutely weird, but I post it here, in the hope that it can help somebody to guess the encoding and what to do with unprintable characters:
- (BOOL)isProblematicString:(NSString *)candidateString {
BOOL returnValue = YES;
if ([candidateString length] <= 2) {
return NO;
}
const char *temp = [candidateString UTF8String];
long length = temp[0];
char *dest = malloc(length + 1);
long ctr = 1;
long usefulCounter = 0;
for (ctr = 1;ctr <= length;ctr++) {
if ((ctr - 1) % 3 == 0) {
memcpy(&dest[ctr - usefulCounter - 1],&temp[ctr],1);
} else {
if (ctr != 1 && ctr < [candidateString length]) {
if (temp[ctr] < 0x10 || temp[ctr] > 0x1F) {
returnValue = NO;
}
}
usefulCounter += 1;
}
}
memset(&dest[length],0,1);
free(dest);
return returnValue;
}
- (NSString *)utf8StringFromUnknownEncodedString:(NSString*)originalUnknownString {
const char *temp = [originalUnknownString UTF8String];
long length = temp[0];
char *dest = malloc(length + 1);
long ctr = 1;
long usefulCounter = 0;
for (ctr = 1;ctr <= length;ctr++) {
if ((ctr - 1) % 3 == 0) {
memcpy(&dest[ctr - usefulCounter - 1],&temp[ctr],1);
} else {
usefulCounter += 1;
}
}
memset(&dest[length],0,1);
NSString *returnValue = [[NSString alloc] initWithUTF8String:dest];
free(dest);
return returnValue;
}
This returns me a string that I can use to build a full UTF8 string. I am looking for a clean solution. Any help is greatly appreciated. Thanks
We're talking about a string which comes from the /Library/Preferences/.GlobalPreferences.plist
(key com.apple.preferences.timezone.new.selected_city).
NSString *city = [[NSUserDefaults standardUserDefaults]
stringForKey:#"com.apple.preferences.timezone.new.selected_city"];
NSLog(#"%#", city); // \^Zt\^\\^]s\^]\^\u\^V\^_q\^]\^[s\^W\^Zx\^P
(lldb) p [city description]
(__NSCFString *) $1 = 0x0000600003f6c240 #"\x1at\x1c\x1ds\x1d\x1cu\x16\x1fq\x1d\x1bs\x17\x1ax\x10"
What I would like to achieve is the following:
Detect in some way that the string has the encoding problem.
Convert the string to UTF8 encoding to use it in the rest of the program.
&
After trying all possible solutions based on dataUsingEncoding and back.
This string has no encoding problem and characters like \x1a, \x1c, ... are valid characters.
You can call dataUsingEncoding: with ASCII, UTF-8, ... but all these characters will still be
present. They're called control characters (or non-printing characters). The linked Wikipedia page explains what these characters are and how they're defined in ASCII, extended ASCII and unicode.
What you're looking for is a way how to remove control characters from a string.
Remove control characters
We can create a category for our new method:
#interface NSString (ControlCharacters)
- (NSString *)stringByRemovingControlCharacters;
#end
#implementation NSString (ControlCharacters)
- (NSString *)stringByRemovingControlCharacters {
// TODO Remove control characters
return self;
}
#end
In all examples below, the city variable is created in this way ...
NSString *city = [[NSUserDefaults standardUserDefaults]
stringForKey:#"com.apple.preferences.timezone.new.selected_city"];
... and contains #"\x1at\x1c\x1ds\x1d\x1cu\x16\x1fq\x1d\x1bs\x17\x1ax\x10". Also all
examples below were tested with the following code:
NSString *cityWithoutCC = [city stringByRemovingControlCharacters];
// tsuqsx
NSLog(#"%#", cityWithoutCC);
// {length = 6, bytes = 0x747375717378}
NSLog(#"%#", [cityWithoutCC dataUsingEncoding:NSUTF8StringEncoding]);
Split & join
One way is to utilize the NSCharacterSet.controlCharacterSet.
There's a stringByTrimmingCharactersInSet:
method (NSString), but it removes these characters from the beginning/end only,
which is not what you're looking for. There's a trick you can use:
- (NSString *)stringByRemovingControlCharacters {
NSArray<NSString *> *components = [self componentsSeparatedByCharactersInSet:NSCharacterSet.controlCharacterSet];
return [components componentsJoinedByString:#""];
}
It splits the string by control characters and then joins these components back. Not a very efficient way, but it works.
ICU transform
Another way is to use ICU transform (see ICU User Guide).
There's a stringByApplyingTransform:reverse:
method (NSString), but it only accepts predefined constants. Documentation says:
The constants defined by the NSStringTransform type offer a subset of the functionality provided by the underlying ICU transform functionality. To apply an ICU transform defined in the ICU User Guide that doesn't have a corresponding NSStringTransform constant, create an instance of NSMutableString and call the applyTransform:reverse:range:updatedRange: method instead.
Let's update our implementation:
- (NSString *)stringByRemovingControlCharacters {
NSMutableString *result = [self mutableCopy];
[result applyTransform:#"[[:Cc:] [:Cf:]] Remove"
reverse:NO
range:NSMakeRange(0, self.length)
updatedRange:nil];
return result;
}
[:Cc:] represents control characters, [:Cf:] represents format characters. Both represents the same character set as the already mentioned NSCharacterSet.controlCharacterSet. Documentation:
A character set containing the characters in Unicode General Category Cc and Cf.
Iterate over characters
NSCharacterSet also offers the characterIsMember: method. Here we need to iterate over characters (unichar) and check if it's a control character or not.
Let's update our implementation:
- (NSString *)stringByRemovingControlCharacters {
if (self.length == 0) {
return self;
}
NSUInteger length = self.length;
unichar characters[length];
[self getCharacters:characters];
NSUInteger resultLength = 0;
unichar result[length];
NSCharacterSet *controlCharacterSet = NSCharacterSet.controlCharacterSet;
for (NSUInteger i = 0 ; i < length ; i++) {
if ([controlCharacterSet characterIsMember:characters[i]] == NO) {
result[resultLength++] = characters[i];
}
}
return [NSString stringWithCharacters:result length:resultLength];
}
Here we filter out all characters (unichar) which belong to the controlCharacterSet.
Other ways
There're other ways how to iterate over characters - for example - Most efficient way to iterate over all the chars in an NSString.
BBEdit & others
Let's write this string to a file:
NSString *city = [[NSUserDefaults standardUserDefaults]
stringForKey:#"com.apple.preferences.timezone.new.selected_city"];
[city writeToFile:#"/Users/zrzka/city.txt"
atomically:YES
encoding:NSUTF8StringEncoding
error:nil];
It's up to the editor how all these controls characters are handled/displayed. Here's en example - Visual Studio Code.
View - Render Control Characters off:
View - Render Control Characters on:
BBEdit displays question marks (upside down), but I'm sure there's a way how to
toggle control characters rendering. Don't have BBEdit installed to verify it.

Matching strings, consider some characters are the same

please help me with this problem.
I want to check if the targetString match the keyword or not. Consider some character may different, but should still return true.
Example:
targetString = #"#ß<"
keyword = #"abc", #"∂B(", #"#Aß<"
result: all must return true.
(Matched.targetString and all keyword are the same.)
Consider me have an array, contains list of character set that can be the same:
NSArray *variants = [NSArray arrayWithObjects:#"aA#∂", #"bBß", #"c©C<(", nil]
So that when matching, with this rule, it can match as the example above.
Here is what i've done so far (using recursion):
- (BOOL) test:(NSString*)aString include:(NSString*) keyWord doTrim:(BOOL)doTrim {
// break recursion.
if([aString length] < [keyWord length]) return false;
// First, loop through each keyword's character
for (NSUInteger i = 0; i < [keyWord length]; i++) {
// Get #"aA#∂", #"bBß", #"c©C<(" or only the character itself.
// like, if the keyword's character is A, return the string #"aA#∂".
// If the character is not in the variants set, eg. P, return #"P"
char c = [keyWord characterAtIndex:i];
NSString *rs = [self variantsWithChar:c];
// Check if rs (#"aA#∂" or #"P") contains aString[i] character
if([rs rangeOfString:[NSString stringWithCharacters:[aString characterAtIndex:i] length:1]].location == NSNotFound) {
// If not the same char, remove first char in targetString (aString), recursion to match again.
return [self test:[aString substringFromIndex:1] include:keyWord doTrim:NO];
}
}
// If all match with keyword, return true.
return true;
}
- (NSString *) variantsWithChar:(char) c {
for (NSString *s in self.variants) {
if ([s rangeOfString:[NSString stringWithFormat:#"%c",c]].location != NSNotFound) {
return s;
}
}
return [NSString stringWithFormat:#"%c", c];
}
The main problem is, variantsWithChar: doesn't return the correct string. I don't know which datatype and which function should I use here. Please help.
For thou who know ruby, here's the example in ruby. It work super fine!
require 'test/unit/assertions'
include Test::Unit::Assertions
class String
def matching?(keyword)
length >= keyword.length && (keyword.chars.zip(chars).all? { |cs| variants(cs[0]).include?(cs[1]) } || slice(1, length - 1).matching?(keyword))
end
private
VARIANTS = ["aA#∂", "bBß", "c©C<("]
def variants(c)
VARIANTS.find { |cs| cs.include?(c) } || c
end
end
assert "abc".matching?("#ß<")
PS: The fact is, it's containt a japanese character set that sounds the same (like あア, いイ... for thou who know japanese)
PS 2: Please feel free to edit this Question, since my engrish is sooo bad. I may not tell all my thought.
PS 3: And, maybe some may comment about the performance. Like, search about 10,000 target words, with nearly 100 variants, each variant have at most 4 more same characters.
So first off, ignore comments about ASCII and stop using char. NSString and CFString use unichar
If what you really want to do is transpose hiragana and katakana you can do that with CFStringTransform()
It wraps the ICU libraries included in OS X and iOS.
It makes it very simple.
Search for that function and you will find examples of how to use it.
After a while (a day) working on the code above, I finally get it through. But don't know about the performance. Someone comment and help me improve about performance, please. Thanks.
- (BOOL) test:(NSString*)aString include:(NSString*) keyWord doTrim:(BOOL)doTrim {
// break recursion.
if([aString length] < [keyWord length]) return false;
// First, loop through each keyword's character
for (NSUInteger i = 0; i < [keyWord length]; i++) {
// Get #"aA#∂", #"bBß", #"c©C<(" or only the character itself.
// like, if the keyword's character is A, return the string #"aA#∂".
// If the character is not in the variants set, eg. P, return #"P"
NSString* c = [NSString stringWithFormat:#"%C", [keyWord characterAtIndex:i]];
NSString *rs = [self variantsWithChar:c];
NSString *theTargetChar = [NSString stringWithFormat:#"%C", [aString characterAtIndex:i]];
// Check if rs (#"aA#∂" or #"P") contains aString[i] character
if([rs rangeOfString:theTargetChar].location == NSNotFound) {
// If not the same char, remove first char in targetString (aString), recursion to match again.
return [self test:[aString substringFromIndex:1] include:keyWord doTrim:NO];
}
}
// If all match with keyword, return true.
return true;
}
If you remove all comment, it'll be pretty short...
////////////////////////////////////////
- (NSString *) variantsWithChar:(NSString *) c{
for (NSString *s in self.variants) {
if ([s rangeOfString:c].location != NSNotFound) {
return s;
}
}
return c;
}
You could try comparing ascii values of the japanese characters in the variants's each character's ascii value. These japanese characters aren't treated like usual characters or string. Hence, string functions like rangeOfString won't work on them.
to be more precise: have a look at the following code.
it will search for "∂" in the string "aA#∂"
NSString *string = #"aA#∂";
NSMutableSet *listOfAsciiValuesOfString = [self getListOfAsciiValuesForString:string]; //method definition given below
NSString *charToSearch = #"∂";
NSNumber *ascii = [NSNumber numberWithInt:[charToSearch characterAtIndex:0]];
int countBeforeAdding = [listOfAsciiValuesOfString count],countAfterAdding = 0;
[listOfAsciiValuesOfString addObject:ascii];
countAfterAdding = [listOfAsciiValuesOfString count];
if(countAfterAdding == countBeforeAdding){ //element found
NSLog(#"element exists"); //return string
}else{
NSLog(#"Doesnt exists"); //return char
}
===================================
-(NSMutableSet*)getListOfAsciiValuesForString:(NSString*)string{
NSMutableSet *set = [[NSMutableSet alloc] init];
for(int i=0;i<[string length];i++){
NSNumber *ascii = [NSNumber numberWithInt:[string characterAtIndex:i]];
[set addObject:ascii];
}
return set;
}

UITEXTVIEW: Get the recent word typed in uitextview

I want to get the most recent word entered by the user from the UITextView.
The user can enter a word anywhere in the UITextView, in the middle or in the end or in the beginning. I would consider it a word when the user finishes typing it and presses a space and does any corrections using the "Suggestions from the UIMenuController".
Example: User types in "kimd" in the UITextView somewhere in the middle of text, he gets a popup for autocorrection "kind" which he does. After he does that, I want to capture "kind" and use it in my application.
I searched a lot on the internet but found solutions that talk about when the user enters text in the end. I also tried detecting a space and then doing a backward search until another space after some text is found, so that i can qualify it as a word. But I think there may be better ways to do this.
I have read somewhere that iOS caches the recent text that we enter in a text field or text view. If I can pop off the top one , that's all I want. I just need handle to that object.
I would really appreciate the help.
Note: The user can enter text anywhere in UItextview. I need the most recent entered word
Thanks.
//This method looks for the recent string entered by user and then takes appropriate action.
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
//Look for Space or any specific string such as a space
if ([text isEqualToString:#" "]) {
NSMutableCharacterSet *workingSet = [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy];
NSRange newRange = [self.myTextView.text rangeOfCharacterFromSet:workingSet
options:NSBackwardsSearch
range:NSMakeRange(0, (currentLocation - 1))];
//The below code could be done in a better way...
UITextPosition *beginning = myTextView.beginningOfDocument;
UITextPosition *start = [myTextView positionFromPosition:beginning offset:currentLocation];
UITextPosition *end = [myTextView positionFromPosition:beginning offset:newRangeLocation+1];
UITextRange *textRange = [myTextView textRangeFromPosition:end toPosition:start];
NSString* str = [self.myTextView textInRange:textRange];
}
}
Here is what I would suggest doing, might seem a little hacky but it would work just fine:
First in .h conform to the UITextViewDelegate and set your text view's delegate to self like this:
myTextView.delegate = self;
and use this code:
- (void)textViewDidChange:(UITextView *)textView { // Delegate method called when any text is modified
if ([textView.text substringFromIndex: [textView.text length] - 1]) { // Gets last character of the text view's text
NSArray *allWords = [[textView text] componentsSeparatedByString: #" "]; // Gets the text view's text and fills an array with all strings seperated by a space in text view's text, basically all the words
NSString *mostRecentWord = [allWords lastObject]; // The most recent word!
}
}
I use this code to get the word behind the #-sign:
- (void)textViewDidChange:(UITextView *)textView {
NSRange rangeOfLastInsertedCharacter = textView.selectedRange;
rangeOfLastInsertedCharacter.location = MAX(rangeOfLastInsertedCharacter.location - 1,0);
rangeOfLastInsertedCharacter.length = 1;
NSString *lastInsertedSubstring;
NSString *mentionSubString;
if (![textView.text isEqualToString:#""]) {
lastInsertedSubstring = [textView.text substringWithRange:rangeOfLastInsertedCharacter];
if (self.startOfMention > 0 || self.startOfHashtag > 0) {
if ([lastInsertedSubstring isEqualToString:#" "] || (self.startOfMention > textView.selectedRange.location || self.startOfHashtag > textView.selectedRange.location)) {
self.startOfMention = 0;
self.lenthOfMentionSubstring = 0;
}
}
if (self.startOfMention > 0) {
self.lenthOfMentionSubstring = textView.selectedRange.location - self.startOfMention;
NSRange rangeOfMentionSubstring = {self.startOfMention, textView.selectedRange.location - self.startOfMention};
mentionSubString = [textView.text substringWithRange:rangeOfMentionSubstring];
dhDebug(#"mentionSubString: %#", mentionSubString);
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
}
}
}
Simple extension for UITextView:
extension UITextView {
func editedWord() -> String {
let cursorPosition = selectedRange.location
let separationCharacters = NSCharacterSet(charactersInString: " ")
let beginRange = Range(start: text.startIndex.advancedBy(0), end: text.startIndex.advancedBy(cursorPosition))
let endRange = Range(start: text.startIndex.advancedBy(cursorPosition), end: text.startIndex.advancedBy(text.characters.count))
let beginPhrase = text.substringWithRange(beginRange)
let endPhrase = text.substringWithRange(endRange)
let beginWords = beginPhrase.componentsSeparatedByCharactersInSet(separationCharacters)
let endWords = endPhrase.componentsSeparatedByCharactersInSet(separationCharacters)
return beginWords.last! + endWords.first!
}
}

Make portion of UITextView undeletable

I have a UITextView and need to make a specific portion un-deletable. Its the first 10 characters of the views text.
I just want it so that if the user is tapping the delete key on the keyboard it simply stops when it reaches say the 10th character in.
Edit
Let me go into a bit more detail.
Let's say the prefix is '123456789:'. I want to be able to type anywhere after this prefix, it can't be editable at all though, so '123456789:' shouldn't not be altered at all. Fichek's answer does this perfectly, however the prefix isn't always there, so how can I detect when it isn't in the textview? I thought the if statement did this but it seems not to.
You can use the delegate method textView:shouldChangeTextInRange:replacementText: To tell the text view whether to accept the delete or not.
As the documentation says:
range : The current selection range. If the length of the range is 0, range reflects the current insertion point. If the user presses the Delete key, the length of the range is 1 and an empty string object replaces that single character.
Edit
Here is an implementation where the user can't delete the the first ten characters. But he will be able to insert characters there.
- (BOOL)textView:(UITextView *)textView shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if (range.length==1 && string.length == 0) {
// Deleting text
if (range.location <= 9) {
return NO;
}
}
return YES;
}
Here is an implementation where he can't modify the first ten characters at all.
- (BOOL)textView:(UITextView *)textView shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if (range.location <= 9) {
return NO;
}
return YES;
}
sch's last edit makes a decent answer, but I want to offer a slightly more flexible approach.
You have to keep in mind the copy/paste system. User might select all the text in text field and try to paste in the entire value which might be perfectly acceptable, but if (range.location <= 9) { return NO; } will reject it. The way I'd do it is put together a string that would be a result of successful edit and then check if that string would start with your desired prefix.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString *resultString = [textField.text stringByReplacingCharactersInRange:range withString:string];
NSLog(#"resulting string would be: %#", resultString);
NSString *prefixString = #"blabla";
NSRange prefixStringRange = [resultString rangeOfString:prefixString];
if (prefixStringRange.location == 0) {
// prefix found at the beginning of result string
return YES;
}
return NO;
}
Edit: if you want to check if the current string in text field starts with the prefix, you can use rangeOfString: the same way:
NSRange prefixRange = [textField.text rangeOfString:prefixString];
if (prefixRange.location == 0) {
// prefix found at the beginning of text field
}
for a complete solution you need to handle several cases, including the cut and paste operations that may start in the uneditable part and extend into the part which the user can edit. I added a variable to control whether or not an operation that includes the uneditable part but extends into the editable part, is valid or not. If valid, the range is adjusted to only affect the editable part.
// if a nil is returned, the change is NOT allowed
- (NSString *)allowChangesToTextView:(UITextView *)textView inRange:(NSRange)changeRange withReplacementText:(NSString *)text
immutableUpTo:(NSInteger)lastReadOnlyChar adjustRangeForEdits:(BOOL)adjustRangeForEdits;
{
NSString *resultString = #"";
NSString *currentText = textView.text;
NSInteger textLength = [currentText length];
// if trying to edit the first part, possibly prevent it.
if (changeRange.location <= lastReadOnlyChar)
{
// handle typing or backspace in protected range.
if (changeRange.length <= 1)
{
return nil;
}
// handle all edits solely in protected range
if ( (changeRange.location + changeRange.length) <= lastReadOnlyChar)
{
return nil;
}
// if the user wants to completely prevent edits that extend into the
// read only substring, return no
if (!adjustRangeForEdits)
{
return nil;
}
// the range includes read only part but extends into editable part.
// adjust the range so that it does not include the read only portion.
NSInteger prevLastChar = changeRange.location + changeRange.length - 1;
NSRange newRange = NSMakeRange(lastReadOnlyChar + 1, prevLastChar - (lastReadOnlyChar + 1) + 1);
resultString = [textView.text stringByReplacingCharactersInRange:newRange withString:text];
return resultString;
}
// the range does not include the immutable part. Make the change and return the string
resultString = [currentText stringByReplacingCharactersInRange:changeRange withString:text];
return resultString;
}
and this is how it gets called from the text view delegate method:
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
// did the user press enter?
if ([text isEqualToString:#"\n"])
{
[textView resignFirstResponder];
return NO;
}
NSInteger endOfReadOnlyText = [self.spotTextLastSet length] - 1;
NSString *newText = [self allowChangesToTextView:textView inRange:range withReplacementText:text
immutableUpTo:endOfReadOnlyText adjustRangeForEdits:YES];
if (newText == nil)
{
// do not allow!
[TipScreen showTipTitle:#"Information" message:#"The first part of the text is not editable. Please add your comments at the end."
ForScreen:#"editWarning"];
return NO;
}
// lets handle the edits ourselves since we got the result string.
textView.scrollEnabled = NO;
textView.text = newText;
// move the cursor to change range start + length of replacement text
NSInteger newCursorPos = range.location + [text length];
textView.selectedRange = NSMakeRange(newCursorPos, 0);
textView.scrollEnabled = YES;
return NO;
}

determine if a string is a number with NSScanner

i'm trying to find out if my string contains any floatValue, and resign the first responder if it's the case, if it's not, the textfield keyboard should stay on screen.
This code always hides the keyboard, even if it's not a floatValue : do you know how to make it work?
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
NSScanner *scan = [NSScanner scannerWithString:[textField text]];
if ([scan scanFloat:NULL]){
[password resignFirstResponder];
[passwordLength resignFirstResponder];
return YES;
} else {
return NO;
}
}
Also, i haven't tried with loops but this is a beginning, if you have any idea :
BOOL doesStringContain(NSString* string, NSString* string2){
for (int i=0; i<[string length]; i++) {
NSString* chr = [string substringWithRange:NSMakeRange(i, 1)];
for (int j=0; j<[string2 length]; j++){
if([chr isEqualToString:j])
return TRUE;
}
}
return FALSE;
}
Thanks a lot
NSScanner will expect that your float be at the beginning of your string. So to combat that we use setCharactersToBeStripped.
setCharactersToBeStripped will filter out all non-numeric non-period characters from the string so that all you're left with to scan is the number that you're looking for.
NSScanner will match int values (without the .) as well as it will equate an int 123 to 123.00000.
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
NSScanner *scan = [NSScanner scannerWithString:[textField text]];
[scan setCharactersToBeSkipped:[[NSCharacterSet characterSetWithCharactersInString:#"1234567890."] invertedSet]];
float f;
if ([scan scanFloat:&f]){
NSLog(#"Scanned a float %f",f);
[textField resignFirstResponder];
return YES;
} else {
NSLog(#"Did not scan a float");
return NO;
}
}
If you want to check if there are non-numeric characters vs only numerics then try :
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
NSCharacterSet *withFloats = [NSCharacterSet characterSetWithCharactersInString:#"0123456789."];
NSCharacterSet *withoutFloats = [NSCharacterSet decimalDigitCharacterSet];
// Change withFloats <-> withoutFloats depending on your need
NSString *newString = [[textField.text componentsSeparatedByCharactersInSet:withFloats] componentsJoinedByString:#""];
NSLog(#"newString %#", newString);
if ([newString isEqualToString:#""]){
NSLog(#"Scanned a numeric");
[textField resignFirstResponder];
return YES;
} else {
NSLog(#"Did not scan a numeric");
return NO;
}
}
You are implementing the wrong delegate method. textFieldShouldReturn: is called when the user hits the return key. (Not when control is trying to "return from" the field, in case that's what you were thinking.)
You should implement textFieldShouldEndEditing: instead. That is where you can (try to) stop the text field from losing first responder status, and keep the keyboard up. Just check the text like you are doing, and return YES or NO (let the system update first responder status).
If you want the return key to dismiss the keyboard if input is valid, you should call endEditing: there. Like this:
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
return [textField endEditing:NO];
}
The NO parameter means don't force it, basically allowing your textFieldShouldEndEditing: code to check the text first. (You should always call endEditing: if you can, rather than resignFirstResponder.)
Note that, for various reasons, the text field might be forced to give up first responder status anyway, so even if you're validating input in this way, be prepared to validate it again before you save it to disk or send it over the network or whatever you want to do with it.