Range of first and last character in the string - ios

I have a string as per below:
$ab$c x$yz$
The string would always start with $ and would end with a $ character. I wish to find the range of the start $ and end $.
I tried: NSRange range = [myStr rangeOfString:#"$"];
I get the output as (NSRange) $0 = location=0 for its location so I am assuming that it is just returning the range of first '$' found in the string.
How do I get range of start $ and end $?
What I am exactly trying to do here is I am using the below method:
- (BOOL)searchBar:(UISearchBar *)searchBar shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
So when I type text as '$' I wish to check if '$' is in between start '$' and end '$'. So I am finding it out using the range. If range of '$' I type is between the range of start '$' and end '$' then do this, else do that.

There is an option for backward search (NSBackwardsSearch). here you can find the correct range of string:
NSRange rangeFirst = [myStr rangeOfString:#"$"],rangeLast=[myStr rangeOfString:#"$" options:NSBackwardsSearch];

I think, what you want is a function like this:
-(BOOL) isRange:(NSRange)range includedIn:(NSString*)fullText {
//Init fullTextRange
NSRange fullTextRange = NSMakeRange(0, 0);
if ([fullText hasPrefix:#"$"] && [fullText hasSuffix:#"$"]) {
//We have a range of start$ and end$
fullTextRange.length = [fullText length] - 1;
}
//Check if range is included in fullTextRange
return (NSIntersectionRange(range, fullTextRange).length == range.length);
}
It returns YES when the range of the text is between start'$' and end'$'.
You should then use it like this:
- (BOOL)searchBar:(UISearchBar *)searchBar shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if ([self isRange:range includedIn:searchBar.text]) {
//Do something
}
else {
//Do something else
}
}

If all you care about is deciding if the text the user is changing does not contain the first or last character (because as you said, the first and last char will always be a '$') this is quite easy.
if(range.location==0)
return NO; //first $
if(range.location + range.length == text.length - 1)
return NO; //last $
//Do whatever in the case you want to allow the edit.

NSRange rangeOfRest = NSRangeMake for 1 past first $ to end of string
NSRange secondDollar = [myStr rangeOfString:#"$" options:0 range:rangeOfRest];

Related

Is there an easy method to check is an NSRange passed to substringWithRange on NSString exists (so not to cause an error)?

Say I pass the NSRange of (location: 5, length: 50) on the NSString "foo", that range obviously doesn't exist.
Is there a way to say [string rangeExists:NSRange] for instance, or do we have to manually validate the input?
You have to write your own check but it's simple enough:
NSString *str = ... // some string
NSRange range = ... // some range to be used on str
if (range.location != NSNotFound && range.location + range.length <= str.length) {
// It's safe to use range on str
}
You could create a category method on NSString that adds your proposed rangeExists: method. It would just be:
- (BOOL)rangeExists:(NSRange)range {
return range.location != NSNotFound && range.location + range.length <= self.length;
}
If range A contains range B, the intersection of A & B should be B, the union of A & B should be A.
So in theory, both
NSEqualRanges(NSUnionRange(A, B), A)
and
NSIntersectionRange(A, B), B)
can be used to check if range A contains range B.
But if we pass a negative value to B.location, for the members of struct NSRange is NSUInteger, it will turn to a very large value. In this case,
NSUnionRange(A, B)
also returns A. It seems to be a bug. (Tested on macOS 10.14, Xcode 10.1)
so we choose NSIntersectionRange, like this:
NSString *str = #"foo";
NSRange textRange = NSMakeRange(0, str.length);
NSRange range = NSMakeRange(0, 1);
if (NSEqualRanges(NSIntersectionRange(textRange, range), range)) {
//safe
}
You can do this :
if(selectedNSRange.location != NSNotFound && NSMaxRange(selectedNSRange) <= self.text.length)

getting certain portion of nsstring

I have string as follows in objective c
NSString *str = #"access_token=E2JmCPLtVySGn-cGGJGGnQ&email=abc#gmail.com";
How can i get only E2JmCPLtVySGn-cGGJGGnQ ?
You can use a Regular Expression (RegEx) to find character patterns.
The pattern matching syntax can be found in the ICU User Guide Regular Expressions
In the example the pattern is: find the first "=" and all characters up to but not including the character "&". In the pattern '(?<=access_token=)" is a look-behind assertion meaning that the "access_token=" must precede the matched text, "[^&]+" the brackets the "[]" mean a character class, the "^" al but the following character, the "+" means one or more.
NSString *str = #"access_token=E2JmCPLtVySGn-cGGJGGnQ&email=abc#gmail.com";
NSString *regexPattern = #"(?<=access_token=)[^&]+";
NSString *found = nil;
NSRange range = [str rangeOfString:regexPattern options:NSRegularExpressionSearch];
if (range.location != NSNotFound) {
found = [str substringWithRange:range];
}
NSLog(#"Range: %#", NSStringFromRange(range));
NSLog(#"found: %#", found);
NSLog output if found:
Range: {13, 22}
found: E2JmCPLtVySGn-cGGJGGnQ
There is a method of the NSString class called rangeOfString: that returns an NSRange struct. If you know that your returned value always has the text access_token= and also includes &email and the format is always the same, you can use this rangeOfString: method to sniff out the token.
NSRange accessTokenRange = [str rangeOfString:#"access_token="];
//this would return (0,13) for index:0, length: 13
NSRange emailRange = [str rangeOfString:#"&email="];
//this would return (34,7)
NSInteger tokenLength = ( emailRange.location + 1 ) - accessTokenRange.length;
//the point where &email begins is at index 34, but it starts at 0
//so it's actually the 35th character
//the access_token= string is 13 characters long, so 35-13 = 22
//so you know that the actual token value is 22 characters long
NSRange trueTokenRange = NSMakeRange(accessTokenRange.length,tokenLength);
NSString *tokenSubstring = [str substringWithRange:trueTokenRange];
I don't think my math is off, zero indexing can introduce off by 1 errors if you're not careful, I usually have NSLog going on each range so I can double check where I need to add or subtract 1. But essentially you'll be starting at the 14th character, which is index 13 of the string, and reading the next 22 characters.

Phone number format ios

In my app I am taking phone number as an input from user. Number should be in US format. I want to display it like (555)-888-888 dynamically. For example when user starts to input number when he reaches to 4 digit it shows number like this (555)-4 and so on. I tried to replaceString method but i found that it will not work.
Look at NBAsYouTypeFormatter class of libPhoneNumber-iOS library.
You create new instance of NSAsYouTypeFormatter with your US region code given:
NBAsYouTypeFormatter *asYouTypeFormatter = [[NBAsYouTypeFormatter alloc] initWithRegionCode:REGION_CODE_STRING];
Then every time user changes the phone number you call:
- (NSString*)inputDigit:(NSString*)nextChar;
or
- (NSString*)removeLastDigit;
Returned NSString from this two methods is your dynamically formatted phone number.
I am going to explain from scratch. So, new users can get the way from start.
Download libPhoneNumber-iOS library from here. At the bottom side of the page of that link, you will find what files you need to add to your project.
Now, follow below steps to implement.
(1) Import files in the view controller where you need your textfield to be formatted.
#import "NBPhoneMetaDataGenerator.h"
#import "NBPhoneNumberUtil.h"
#import "NBAsYouTypeFormatter.h"
and make instance of type NBAsYouTypeFormatter in header file:
NBAsYouTypeFormatter *asYouTypeFormatter;
(2) In the viewDidLoad method of that view controller, initialize that object taken earlier:
asYouTypeFormatter = [[NBAsYouTypeFormatter alloc] initWithRegionCode:#"IN"];
Note: #"IN" is for India. You can set it to anything you want. Refer to plist file that will be included in libPhoneNumber-iOS library to view full list of region codes.
(3) In delegate method of UITextField, dynamically manage text of yout textfield.
#pragma mark
#pragma mark - Phone Number textfield formatting
# define LIMIT 18 // Or whatever you want
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
// Just allow 18 digits
if(!(([string length] + range.location) > LIMIT))
{
// Something entered by user
if(range.length == 0)
{
[txtNumber setText:[asYouTypeFormatter inputDigit:string]];
}
// Backspace
else if(range.length == 1)
{
[txtNumber setText:[asYouTypeFormatter removeLastDigit]];
}
}
return NO;
}
Hope it helps !!!
I found a solution that I wanted to share because, even with the solutions previously presented here, I had a hard time finding how to make it work.
I have a tableView whose cells include a textField. One of this cells bear the phone number. It can be already filled-in in some cases, or not.
This is in Swift by the way.
Make sure your bridging header file nameOfYourProject-Bridging-Header includes the following line:
#import "NBAsYouTypeFormatter.h"
Declare a property for the NBAsYouTypeFormatter:
private var phoneFormatter: NBAsYouTypeFormatter!
in viewDidLoad, or didSet of a property, initialize the NBAsYouTypeFormatter with the country code:
// yourRegionCode is a 2-digit country code (ISO 3166)
phoneFormatter = NBAsYouTypeFormatter(regionCode: yourRegionCode)
Declare your viewController as a TextFieldDelegate and implement function shouldChangeCharactersInRange:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
// Phone number cell
if cellContainsPhoneNumber { // This is specific to your own tableView
// Formatting phone number as you type
let textWithoutSpaces = textField.text.stringByReplacingOccurrencesOfString(" ", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
phoneFormatter.inputString(textWithoutSpaces) // This is the initial value of the phoneFormatter each time the delegate method is called
let formattedNumber: String!
if string == "" {
formattedNumber = phoneFormatter.removeLastDigit()
} else {
formattedNumber = phoneFormatter.inputDigit(string)
}
// set the textField text with the new formattedNumber
textField.text = formattedNumber
return false
}
return true
}
This way, it works exactly as Apple's contact edition mechanism.
Let me know if this helped you.
Here's an updated snippet that generally works for me (Swift 2.0):
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
// Allow up to 18 chars
if !(string.characters.count + range.location > 18) {
if range.length == 0 {
// original text didn't change
textField.text = phoneFormatter?.inputDigit(string)
} else if range.length == 1 {
// user pressed backspace
textField.text = phoneFormatter?.removeLastDigit()
} else if range.length == textField.text?.characters.count {
// text was cleared
phoneFormatter?.clear()
textField.text = ""
}
}
return false
}
The main thing that changed was it allows for the user to press the "Clear" button or Select All -> Clear.
There are some edge cases such as the user editing specific digits in the phone number which this doesn't handle but could be easily added.
Here is a solution using libPhoneNumber that also handles the non trivial cases of editing in the middle of the number, cutting and pasting, selection and typing. It keeps the cursor stable and does not behave unexpectedly.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if(range.location == textField.text.length && range.length == 0)
{
// Something added at end
textField.text = [numberFormatter inputDigit:string];
}
else if(range.location == textField.text.length-1 && range.length == 1)
{
// Backspace at end
textField.text = [numberFormatter removeLastDigit];
} else {
// Other modification in middle
NSString* input = textField.text;
// New cursor position after modification
NSUInteger cursorIdx = range.location + string.length;
// If backspacing to delete a format character - just reposition the cursor.
BOOL backspaceOnly = range.length == 1 && string.length == 0 && !isdigit([input characterAtIndex:range.location]);
if(!backspaceOnly) {
// make the modification, reformat the number
input = [input stringByReplacingCharactersInRange:range withString:string];
[numberFormatter clear];
BOOL rememberCursorPos = NO;
NSString* text;
// reinput the number to the formatter
// remembering the first digit position at or after the cursor
for (int i = 0; i < input.length; ++i)
{
if(i == cursorIdx) {
rememberCursorPos = YES;
}
char digit = [input characterAtIndex:i];
switch(digit) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if(!rememberCursorPos) {
text = [numberFormatter inputDigit:[NSString stringWithFormat:#"%c", digit]];
} else {
text = [numberFormatter inputDigitAndRememberPosition:[NSString stringWithFormat:#"%c", digit]];
rememberCursorPos = NO;
}
break;
}
}
// reformat the number
textField.text = text;
// get updated cursor position (formatter position starts at 1)
cursorIdx = numberFormatter.getRememberedPosition - 1;
}
// reposition the cursor
UITextPosition* position = [textField positionFromPosition:textField.beginningOfDocument offset:cursorIdx];
textField.selectedTextRange = [textField textRangeFromPosition:position toPosition:position];
}
return NO;
}
You can use this library for formatting input during typing https://github.com/luximetr/AnyFormatKit
Example
let textInputController = TextInputController()
let textInput = TextInputField() // or TextInputView or any TextInput
textInputController.textInput = textInput // setting textInput
let formatter = TextInputFormatter(textPattern: "### (###) ###-##-##", prefix: "+12")
textInputController.formatter = formatter // setting formatter
In this case TextInputController will format text in your textField or textView.

Limiting user input in a UITextField to a certain range of numbers (not number of characters)

I would like to limit user input in a UITextField to 1-105. I have set the delegate and have successfully limited the actual number of characters via the following code, found elsewhere on Stackoverflow. Is there something that I can add in order to force the user to input any integer between 1 and 105?
#define MAXLENGTH 2
- (BOOL)textField:(UITextField *) textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSUInteger oldLength = [_startLevel.text length];
NSUInteger replacementLength = [string length];
NSUInteger rangeLength = range.length;
NSUInteger newLength = oldLength - rangeLength + replacementLength;
BOOL returnKey = [string rangeOfString: #"\n"].location != NSNotFound;
return newLength <= MAXLENGTH || returnKey;
}
I am using the number keypad, so the user is already limited to entering numbers. I just need to find something that will make them input something in the range.
Thanks in advance.
First convert the string to a number. In order of ease of use and lack of control, the ways you do that are -[NSString integerValue]*, NSNumberFormatter, and NSScanner. The formatter will give you an NSNumber from which you can then get the integerValue*; the other two get you primitives directly.
Once you have that, compare the number to the endpoints of your range, creating a boolean. Combine that boolean with the other two -- for length and lack of newline -- you already have, and return the result.
*For floating point, either floatValue or doubleValue.
In didEndEditing, get the text, convert it to an integer, and check the value. If it's out of range, display an error message. You might also reset the text to it's previous value, assuming it starts out in-range.
I've used a regular expression to validate inputs to the right format. I found some documentation on line on RegEx that I was able to use to build my expression. You might create a regular expression that requires the input to be 1, 2, or 3 digits. I'm no expert, but the string #"^[0-9]{1,3}$" should require 1 to 3 digits between 0 and 9 (the 0-9 defines the legal characters, and the {1,3} means that the user can enter 1 through 3 of them. The ^ at the beginning anchors the expression to the beginning of the string, and the "$" at the end anchors the expression to the end of the string.)
You can use the code below
NSString *numberRegex = #"[1-9]||[0-9][1-9]||[0-1]0[0-5]";
NSPredicate *emailTest = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", numberRegex];
BOOL b = [emailTest evaluateWithObject:numberField.text];
if (b)
{
//Code when number is in the range 1-105
}
else
{
//Code when number is not in the range 1-105
}
Hope this helps you

rangeOfString and NSNotFound result not what i expected

I'm at bit of a loss here, I'm obviously doing something wrong and haven't grasped the way rangeOfString works.
I have a number of textfields and on certain of them, the phone and fax ones, i want to only permit a small range of values 1234567890+-() to be entered
I'm trying to use textfield: shouldChangeCharactersInRange: replacementString: to not return the characters if they fall outside the specified set of characters.
My method is below and below that is an example of the log .
The code for not returning a ';' works fine, as does the code making sure its only the phone and fax number fields that are being tested.
however what happens is it doesn't matter what character i enter - a '1' or '2' a letter or symbol, i get the same result NSNotFound.
Any idea where i am going wrong ?
Cheers
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if ([string isEqualToString:#";"]) {
return NO;
}
if( ([textField.placeholder isEqualToString:self.locationFieldNames[phoneNumber]]) | ([textField.placeholder isEqualToString:self.locationFieldNames[faxNumber]])){
NSString *includeString = #"1234567890-()+";
if ([string rangeOfString:includeString].location == NSNotFound) {
DLog(#"%# is NSNOTFOUND", string);
return NO;
} else {
DLog(#"%# is !NSNOTFOUND", string);
return YES;
}
}
DLog(#"got here");
return YES;
}
and example from the log
2013-11-12 12:59:19.832 SplashDL[4138:70b] -[DiveFacilityDetailViewController textField:shouldChangeCharactersInRange:replacementString:] 1 is NSNOTFOUND
2013-11-12 12:59:24.556 SplashDL[4138:70b] -[DiveFacilityDetailViewController textField:shouldChangeCharactersInRange:replacementString:] q is NSNOTFOUND
2013-11-12 12:59:26.806 SplashDL[4138:70b] -[DiveFacilityDetailViewController textField:shouldChangeCharactersInRange:replacementString:] ! is NSNOTFOUND
You have made misstake in following line of code:
if ([string rangeOfString:includeString].location == NSNotFound)
Change it to following:
if ([includeString rangeOfString:string].location == NSNotFound)
Also note that user may not only type text but he can also copy/paste it from somewhere. So the length of string may be greater than 1.

Resources