Get string between strings with multiple occurrences - ios

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.

Related

NSString search for character from the location of the cursor in a UITextView

I am stuck with trying to figure out how I can find the occurrence of a character from a specified range (i.e. the current position of the cursor).
I know how to find the current position of the cursor using
NSRange cursorPosition = [textView.text selectedRange];
But I am trying to figure out how I can search backward from cursorPosition.
What I am trying to do is, for example, if I have a string:
NSString *string = #"Hello I am tagging #xyz to notify them of the tag"
Suppose in string, the location of the cursor is just before "to", I want to search from the location of the cursor to the location of # in the string and take the substring from the cursorPosition to #.
Please let me know if my description is vague or not well written, I will explain it further.
Any help would be great! Thanks a lot!
Edit: Thanks a lot for all your time and responses!
Simply use rangeOfString:options:range:.
// Get the current selection range
NSRange cursorPosition = [textView.text selectedRange];
if (cursorPosition.location != NSNotFound) {
// Build range from start of text up to the start of the selection
NSRange searchRange = NSRangeMake(0, cursorPosition.location);
// Find the desired substring within the range
NSRange matchRange = [textView.text rangeOfString:#"#" options:NSBackwardsSearch range:searchRange];
if (matchRange.location != NSNotFound) {
// Build range starting with the found substring up to the start of the selection
NSRange textRange = NSRangeMake(matchRange.location, searchRange.length - matchRange.location);
// Get the text in the desired range
NSString *matchingText = [textView.text substringInRange:textRange];
NSLog(#"Matching text to caret is %#", matchingText);
} else {
NSLog(#"No match up to the caret");
}
} else {
NSLog(#"No selection");
}
Note that this code looks up to the start of any current selection in the text view. If you want to search within the current selection you will need to adjust accordingly.
Try this
NSRange cursorPosition = [textView.text selectedRange];
NSString *string = #"Hello I am tagging #xyz to notify them of the tag";
NSInteger loc = [string rangeOfString : #"#"].location;
NSRange range = NSMakeRange(loc, (cursorPosition.location - loc));
NSString *newString = [string substringWithRange: range];
NSInteger cursorPosition = [_textView selectedRange].location;
NSInteger stringPosition = [_textView.text rangeOfString:#"#"].location;
if (stringPosition < cursorPosition) {
stringPosition = stringPosition + 1;
}
NSInteger lengthFinal = labs(cursorPosition - stringPosition);
NSRange range = NSMakeRange(MIN(cursorPosition, stringPosition), lengthFinal);
NSString *finalSubString = [_textView.text substringWithRange:range];
It is working for me. Place cursor and enter back button to test
-(BOOL)textView:(nonnull UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(nonnull NSString *)text{
NSRange rangeFrom = [textView.text rangeOfString:#"#"];
NSString *result = [textView.text substringWithRange:NSMakeRange(rangeFrom.location+1, range.location-rangeFrom.location)];
NSLog(#"result:%#",result);//result:xyz
return YES;
}
// ...........************ check with this....Why not is this working....*/
NSRange cursorPosition = NSMakeRange(23, 1);//[textView.text range];//selectedRange
NSString *string = #"Hello I am tagging #xyz to notify them of the tag";
NSRange rangeFrom = [string rangeOfString:#"#"];
NSString *result = [string substringWithRange:NSMakeRange(rangeFrom.location+1, cursorPosition.location-rangeFrom.location)];
NSLog(#"result:%#",result);//result:xyz

Objective-c Extracting multiple variables from Big string (performance improvements)

I need to extract multiple variables to array from big string 0,5mb .This script is working when input string is smaller ~50kb and around 100 items to find. but target is around 7k items to extract from 0.5mb string. When there is less than 300 items it works. but finish after few sec. But if more im getting issue:
Communications error: <OS_xpc_error: <error: 0x321f9614> { count = 1, contents = "XPCErrorDescription" => <string: 0x321f986c> { length = 22, contents = "Connection interrupted" }
Code
NSUInteger count = 0, length = [dataString length];
NSRange range = NSMakeRange(0, length);
while(range.location != NSNotFound)
{
range = [dataString rangeOfString: #"userID_X" options:0 range:range];
if(range.location != NSNotFound)
{
range = NSMakeRange(range.location + range.length, length - (range.location + range.length));
count++;
}
}
NSLog(#"Count:%i",count);
realTokens = [NSMutableArray new];
realIds = [NSMutableArray new];
for (int gg=0; gg<count-1; gg++) {
NSArray *realidtemp;
NSArray *realtokentemp;
realidtemp = [[[dataString componentsSeparatedByString:#"userID_X"]objectAtIndex:1] componentsSeparatedByString:#"."];
realtokentemp = [[[dataString componentsSeparatedByString:#"userToken"]objectAtIndex:1] componentsSeparatedByString:#"."];
NSString *checkTkn = realtokentemp[0];
NSString *checkId = realidtemp[0];
if (![checkTkn containsString:#"EMPTY"] && ![checkId containsString:#"EMPTY"]) {
[realTokens addObject:checkTkn];
[realIds addObject:checkId];
NSString *fix1 = #"userToken";
NSString *fix2 = #"userID_X";
fix1 = [fix1 stringByAppendingString:checkTkn];
fix2 = [fix2 stringByAppendingString:checkId];
dataString = [dataString stringByReplacingOccurrencesOfString:fix1 withString:#"EMPTY"];
dataString = [dataString stringByReplacingOccurrencesOfString:fix2 withString:#"EMPTY"];
}
}
You perform many operations with a runtime proportional to the number of characters. That's something you need to avoid. You are also creating a huge number of huge autoreleased strings. Each time you call stringByReplacing... you create a new 0.5MB string. On an iPhone with 1GB RAM, 2,000 calls fill your RAM completely but you will crash earlier. componentsSeparatedByString is just as bad.
Create an NSMutableString for the result, and only add the pieces that you want to add. And please please please don't ask how to do that.

Find substring range of NSString with unicode characters

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

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

How to Split NSString to multiple Strings after certain number of characters

I am developing an iOS app using Xcode 4.6.2.
My app receives from the server lets say for example 1000 characters which is then stored in NSString.
What I want to do is: split the 1000 characters to multiple strings. Each string must be MAX 100 characters only.
The next question is how to check when the last word finished before the 100 characters so I don't perform the split in the middle of the word?
A regex-based solution:
NSString *string = // ... your 1000-character input
NSString *pattern = #"(?ws).{1,100}\\b";
NSError *error = NULL;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern: pattern options: 0 error: &error];
NSArray *matches = [regex matchesInString:string options:0 range:NSMakeRange(0, [string length])];
NSMutableArray *result = [NSMutableArray array];
for (NSTextCheckingResult *match in matches) {
[result addObject: [string substringWithRange: match.range]];
}
The code for the regex and the matches part is taken directly from the docs, so the only difference is the pattern.
The pattern basically matches anything from 1 to 100 characters up to a word boundary. Being a greedy pattern, it will give the longest string possible while still ending with a whole word. This ensures that it won't split any words in the middle.
The (?ws) makes the word recognition work with Unicode's definition of word breaks (the w flag) and treat a line end as any other character (the s flag).
Notice that the algorithm doesn't handle "words" with more than 100 characters well - it will give you the last 100 characters and drop the first part, but that should be a corner case.
(assuming your words are separated by a single space, otherwise use rangeOfCharacterFromSet:options:range:)
Use NSString -- (NSRange)rangeOfString:(NSString *)aString options:(NSStringCompareOptions)mask range:(NSRange)aRange with:
aString as #" "
mask as NSBackwardsSearch
Then you need a loop, where you check that you haven't already got to the end of the string, then create a range (for use as aRange) so that you start 100 characters along the string and search backwards looking for the space. Once you find the space, the returned range will allow you to get the string with substringWithRange:.
(written freehand)
NSRange testRange = NSMakeRange(0, MIN(100, sourceString.length));
BOOL complete = NO;
NSMutableArray *lines = [NSMutableArray array];
while (!complete && (testRange.location + testRange.length) < sourceString.length) {
NSRange hitRange = [sourceString rangeOfString:#"" options:NSBackwardsSearch range:testRange];
if (hitRange.location != NSNotFound) {
[lines addObject:[sourceString substringWithRange:hitRange];
} else {
complete = YES;
}
NSInteger index = hitRange.location + hitRange.length;
testRange = NSMakeRange(index, MIN(100, sourceString.length - index));
}
This can help
- (NSArray *)chunksForString(NSString *)str {
NSMutableArray *chunks = [[NSMutableArray alloc] init];
double sizeChunk = 100.0; // or whatever you want
int length = 0;
int loopSize = ceil([str length]/sizeChunk);
for (int index = 0; index < loopSize; index++) {
NSInteger newRangeEndLimit = ([str length] - length) > sizeChunk ? sizeChunk : ([str length] - length);
[chunks addObject:[str substringWithRange:NSMakeRange(length, newRangeEndLimit)];
length += 99; // Minus 1 from the sizeChunk as indexing starts from 0
}
return chunks;
}
use NSArray *words = [stringFromServer componentsSeparatedBy:#" "];
this will give you words.
if you really need to make it nearest to 100 characters, start appending strings maintaining the total length of the appended strings and check that it should stay < 100.

Resources