Find substring range of NSString with unicode characters - ios

If I have a string like this.
NSString *string = #"😀1😀3😀5😀7😀"
To get a substring like #"3😀5" you have to account for the fact the smiley face character take two bytes.
NSString *substring = [string substringWithRange:NSMakeRange(5, 4)];
Is there a way to get the same substring by using the actual character index so NSMakeRange(3, 3) in this case?

Thanks to #Joe's link I was able to create a solution that works.
This still seems like a lot of work for just trying to create a substring at unicode character ranges for an NSString. Please post if you have a simpler solution.
#implementation NSString (UTF)
- (NSString *)substringWithRangeOfComposedCharacterSequences:(NSRange)range
{
NSUInteger codeUnit = 0;
NSRange result;
NSUInteger start = range.location;
NSUInteger i = 0;
while(i <= start)
{
result = [self rangeOfComposedCharacterSequenceAtIndex:codeUnit];
codeUnit += result.length;
i++;
}
NSRange substringRange;
substringRange.location = result.location;
NSUInteger end = range.location + range.length;
while(i <= end)
{
result = [self rangeOfComposedCharacterSequenceAtIndex:codeUnit];
codeUnit += result.length;
i++;
}
substringRange.length = result.location - substringRange.location;
return [self substringWithRange:substringRange];
}
#end
Example:
NSString *string = #"😀1😀3😀5😀7😀";
NSString *result = [string substringWithRangeOfComposedCharacterSequences:NSMakeRange(3, 3)];
NSLog(#"%#", result); // 3😀5

Make a swift extension of NSString and use new swift String struct. Has a beautifull String.Index that uses glyphs for counting characters and range selecting. Very usefull is cases like yours with emojis envolved

Related

Get string between strings with multiple occurrences

I found a lot of examples how to find string between 2 strings, but none of which shows how to handle multiple occurrences of that string. I have for example string like this
"Hi, I am <id>User</id>. I am 20 <id>years old</id>, and live in <id>some country</id>."
The idea behind is that I want to hyperlink each occurrence of that string within UITextField, and remove tags from the string. I also have 2 types of the tag, one should display hyperlink, the other should popup alert view with some text description of the word or phrase clicked.
EDIT:
Found a perfectly good working solution to extend this logic with changing content of the text with attributed string between tags provided in the text. Link here.
#Aleksandar Try this.. it works for me..
NSString *serverOutput = #"Hi, I am <id>User</id>. I am 20 <id>years old</id>, and live in <id>some country</id>.";
if([serverOutput containsString:#"</id>"])
{
NSArray *arrSeparate = [serverOutput componentsSeparatedByString:#"</id>"];
NSString *output = #"";
for(int i=0; i<arrSeparate.count; i++)
{
if([[arrSeparate objectAtIndex:i] containsString:#"<id>"])
{
NSArray *arrTest = [[arrSeparate objectAtIndex:i] componentsSeparatedByString:#"<id>"];
if(output.length < 1)
output = [arrTest objectAtIndex:1];
else
output = [NSString stringWithFormat:#"%#\n%#",output,[arrTest objectAtIndex:1]];
}
}
serverOutput = output;
}
NSLog(#"%#", serverOutput);
Please look into this, and i hope this gets you all the range where the keyword exists
NSString *serverOutput = #"Hi, I am <id>User</id>. I am 20 <id>years old</id>, and live in <id>some country</id>";
NSUInteger count = 0, length = [serverOutput length];
NSRange startRange = NSMakeRange(0, length);
NSRange endRange = NSMakeRange(0, length);
while(startRange.location != NSNotFound)
{
startRange = [serverOutput rangeOfString: #"<id>" options:0 range:startRange];
if(startRange.location != NSNotFound)
{
endRange = [serverOutput rangeOfString: #"</id>" options:0 range:endRange];
startRange = NSMakeRange(startRange.location + startRange.length, length - (startRange.location + startRange.length));
endRange = NSMakeRange(endRange.location + endRange.length, length - (endRange.location + endRange.length));
count++;
}
}
startRange will be the range from where the tag starts and endRange is where starts
You can change the range, location, create attributed string and add hyperlink as the range of string is available to you.

How to convert a NSString to NSInteger with the sum of ASCII values?

In my Objective-C code I'd like to take a NSString value, iterate through the letters, sum ASCII values of the letters and return that to the user (preferably as the NSString too).
I have already written a loop, but I don't know how to get the ASCII value of an individual character. What am I missing?
- (NSString*) getAsciiSum: (NSString*) input {
NSInteger sum = 0;
for (NSInteger index=0; index<input.length; index++) {
sum = sum + (NSInteger)[input characterAtIndex:index];
}
return [NSString stringWithFormat: #"%#", sum];
}
Note: I've seen similar questions related to obtaining ASCII values, but all of them ended up displaying the value as a string. I still don't know how to get ASCII value as NSInteger.
Here is the answer:
- (NSString *) getAsciiSum: (NSString *) input
{
NSString *input = #"hi";
int sum = 0;
for (NSInteger index = 0; index < input.length; index++)
{
char c = [input characterAtIndex:index];
sum = sum + c;
}
return [NSString stringWithFormat: #"%d", sum]);
}
This is working for me.
Hope this helps!
This should work.
- (NSInteger)getAsciiSum:(NSString *)stringToSum {
int asciiSum = 0;
for (int i = 0; i < stringToSum.length; i++) {
NSString *character = [stringToSum substringWithRange:NSMakeRange(i, 1)];
int asciiValue = [character characterAtIndex:0];
asciiSum = asciiSum + asciiValue;
}
return asciiSum;
}
Thank you to How to convert a NSString to NSInteger with the sum of ASCII values? for the reference.

NSString to treat "regular english alphabets" and characters like emoji or japanese uniformly

There is a textView in which I can enter Characters. characters can be a,b,c,d etc or a smiley face added using emoji keyboard.
-(void)textFieldDidEndEditing:(UITextField *)textField{
NSLog(#"len:%lu",textField.length);
NSLog(#"char:%c",[textField.text characterAtIndex:0]);
}
Currently , The above function gives following outputs
if textField.text = #"qq"
len:2
char:q
if textField.text = #"😄q"
len:3
char:=
What I need is
if textField.text = #"qq"
len:2
char:q
if textField.text = #"😄q"
len:2
char:😄
Any clue how to do this ?
Since Apple screwed up emoji (actually Unicode planes above 0) this becomes difficult. It seems it is necessary to enumerate through the composed character to get the actual length.
Note: The NSString method length does not return the number of characters but the number of code units (not characters) in unichars. See NSString and Unicode - Strings - objc.io issue #9.
Example code:
NSString *text = #"qqq😄rrr";
int maxCharacters = 4;
__block NSInteger unicharCount = 0;
__block NSInteger charCount = 0;
[text enumerateSubstringsInRange:NSMakeRange(0, text.length)
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
unicharCount += substringRange.length;
if (++charCount >= maxCharacters)
*stop = YES;
}];
NSString *textStart = [text substringToIndex: unicharCount];
NSLog(#"textStart: '%#'", textStart);
textStart: 'qqq😄'
An alternative approach is to use utf32 encoding:
int byteCount = maxCharacters*4; // 4 utf32 characters
char buffer[byteCount];
NSUInteger usedBufferCount;
[text getBytes:buffer maxLength:byteCount usedLength:&usedBufferCount encoding:NSUTF32StringEncoding options:0 range:NSMakeRange(0, text.length) remainingRange:NULL];
NSString * textStart = [[NSString alloc] initWithBytes:buffer length:usedBufferCount encoding:NSUTF32LittleEndianStringEncoding];
There is some rational for this in Session 128 - Advance Text Processing from 2011 WWDC.
This is what i did to cut a string with emoji characters
+(NSUInteger)unicodeLength:(NSString*)string{
return [string lengthOfBytesUsingEncoding:NSUTF32StringEncoding]/4;
}
+(NSString*)unicodeString:(NSString*)string toLenght:(NSUInteger)len{
if (len >= string.length){
return string;
}
NSInteger charposition = 0;
for (int i = 0; i < len; i++){
NSInteger remainingChars = string.length-charposition;
if (remainingChars >= 2){
NSString* s = [string substringWithRange:NSMakeRange(charposition,2)];
if ([self unicodeLength:s] == 1){
charposition++;
}
}
charposition++;
}
return [string substringToIndex:charposition];
}

Backward with custom string

I used a string array for emoticons like this:
NSArray *emoticons = #[#"[smile]",#"[cry]",#"[happy]" ...]
then in a UITextView displaying a string like this:
I'm so happy now [happy] now [smile]
When I click a backward or delete button, if the last word is in emoticons, I want a whole emoticon string be deleted, not the last one character only.
Any idea?
Try this,
NSString *string = self.textView.text;
__block NSString *deleteWord = nil;
__block NSRange rangeOfWord;
[string enumerateSubstringsInRange:NSMakeRange(0, self.textView.selectedRange.location + self.textView.selectedRange.length) options:NSStringEnumerationByWords | NSStringEnumerationReverse usingBlock:^(NSString *substring, NSRange subrange, NSRange enclosingRange, BOOL *stop) {
deleteWord = substring;
rangeOfWord = enclosingRange;
*stop = YES;
}];
if ([emoticons containsObject:deleteWord]) {
string = [string stringByReplacingCharactersInRange:rangeOfWord withString:#""];
self.textView.text = string;
self.textView.selectedRange = NSMakeRange(rangeOfWord.location, 0);
}
You might achieve something like this with the UITextViewDelegate method textView:shouldChangeTextInRange:replacementText: checking what is about to be deleted and remove the whole [emoticon] word.
I am giving you the idea that i used.
as you do not mentioned what you used as emoticons.
but for delete logic i think you will get idea from my this code.
if ([string isEqualToString:#""]) {
NSString *lastChar = [txthiddenTextField.text substringFromIndex: [txthiddenTextField.text length] - 1];
NSLog(#"Last char:%#",lastChar);
txthiddenTextField.text = [txthiddenTextField.text substringToIndex:[txthiddenTextField.text length] - 1];
NSString *strPlaceHolder;
strPlaceHolder = txthiddenTextField.text;
if([lastChar isEqualToString:#"]"])
{
int j = 1;
for (int i = [txthiddenTextField.text length]-1; i >=0; --i)
{
NSString *lastChar = [txthiddenTextField.text substringFromIndex: [txthiddenTextField.text length] - 1];
if([lastChar isEqualToString:#"["])
{
NSLog(#"%d",j);
txthiddenTextField.text = [txthiddenTextField.text substringToIndex:[txthiddenTextField.text length] - 1];
// NSLog(#"Processing character %#",strPlaceHolder);
break;
}
txthiddenTextField.text = [txthiddenTextField.text substringToIndex:[txthiddenTextField.text length] - 1];
j = j+1;
}
}
NSLog(#"My text fild value :%#",txthiddenTextField.text);
return YES;
}
So, from here you have to check if the closing bracket is coming or not.
if closing bracket will come then up to opening bracket you have to delete.
then whole emoticon will delete.
hope this helps....

How to split string into substrings on iOS?

I received an NSString from the server. Now I want to split it into the substring which I need.
How to split the string?
For example:
substring1:read from the second character to 5th character
substring2:read 10 characters from the 6th character.
You can also split a string by a substring, using NString's componentsSeparatedByString method.
Example from documentation:
NSString *list = #"Norman, Stanley, Fletcher";
NSArray *listItems = [list componentsSeparatedByString:#", "];
NSString has a few methods for this:
[myString substringToIndex:index];
[myString substringFromIndex:index];
[myString substringWithRange:range];
Check the documentation for NSString for more information.
I wrote a little method to split strings in a specified amount of parts.
Note that it only supports single separator characters. But I think it is an efficient way to split a NSString.
//split string into given number of parts
-(NSArray*)splitString:(NSString*)string withDelimiter:(NSString*)delimiter inParts:(int)parts{
NSMutableArray* array = [NSMutableArray array];
NSUInteger len = [string length];
unichar buffer[len+1];
//put separator in buffer
unichar separator[1];
[delimiter getCharacters:separator range:NSMakeRange(0, 1)];
[string getCharacters:buffer range:NSMakeRange(0, len)];
int startPosition = 0;
int length = 0;
for(int i = 0; i < len; i++) {
//if array is parts-1 and the character was found add it to array
if (buffer[i]==separator[0] && array.count < parts-1) {
if (length>0) {
[array addObject:[string substringWithRange:NSMakeRange(startPosition, length)]];
}
startPosition += length+1;
length = 0;
if (array.count >= parts-1) {
break;
}
}else{
length++;
}
}
//add the last part of the string to the array
[array addObject:[string substringFromIndex:startPosition]];
return array;
}

Resources