Split NSString and keep split character - ios

I have a string with some new line characters in it and I need to split it up. currently I'm using:
NSArray *a = [string componentsSeperatedByString:#"\n"];
However, this gets rid of all the new line characters. How can I keep those elements as part of the array?

Split the string yourself.
NSMutableArray *lines = [NSMutableArray array];
NSRange searchRange = NSMakeRange(0, string.length);
while (1) {
NSRange newlineRange = [string rangeOfString:#"\n" options:NSLiteralSearch range:searchRange];
if (newlineRange.location != NSNotFound) {
NSInteger index = newlineRange.location + newlineRange.length;
NSString *line = [string substringWithRange:NSMakeRange(searchRange.location, index - searchRange.location)];
[lines addObject:line];
searchRange = NSMakeRange(index, string.length - index);
} else {
break;
}
}
NSLog(#"lines = %#", lines);

As far as I know there's no API for doing it. A simple solution would be to build a second array starting from the components, as follows
NSString *separator = #".";
NSArray *components = [#"ab.c.d.ef.gh" componentsSeparatedByString:separator];
NSMutableArray *finalComponents = [NSMutableArray arrayWithCapacity:components.count * 2 - 1];
[components enumerateObjectsUsingBlock:^(id component, NSUInteger idx, BOOL *stop) {
[finalComponents addObject:component];
if (idx < components.count - 1) {
[finalComponents addObject:separator];
}
}];
NSLog(#"%#", finalComponents); // => ["ab", ".", "c", ".", "d", ".", "ef", ".", "gh"]
Not extremely efficient, but probably not a huge concern unless dealing with a very high number of components.

Related

Regular Expression and NSAttributedString to change colour of all the same words UILabel [duplicate]

There is a substring that occurs in a string several times. I use rangeOfString, but it seems that it can only find the first location. How can I find all the locations of the substring?
NSString *subString1 = #"</content>";
NSString *subString2 = #"--\n";
NSRange range1 = [newresults rangeOfString:subString1];
NSRange range2 = [newresults rangeOfString:subString2];
int location1 = range1.location;
int location2 = range2.location;
NSLog(#"%i",location1);
NSLog(#"%i",location2);
You can use rangeOfString:options:range: and set the third argument to be beyond the range of the first occurrence. For example, you can do something like this:
NSRange searchRange = NSMakeRange(0,string.length);
NSRange foundRange;
while (searchRange.location < string.length) {
searchRange.length = string.length-searchRange.location;
foundRange = [string rangeOfString:substring options:0 range:searchRange];
if (foundRange.location != NSNotFound) {
// found an occurrence of the substring! do stuff here
searchRange.location = foundRange.location+foundRange.length;
} else {
// no more substring to find
break;
}
}
Swift 3.0
Find all locations of substring i
let text = "This is the text and i want to replace something"
let mutableAttributedString = NSMutableAttributedString(string: text)
var searchRange = NSRange(location: 0, length: text.characters.count)
var foundRange = NSRange()
while searchRange.location < text.characters.count {
searchRange.length = text.characters.count - searchRange.location
foundRange = (text as NSString).range(of: "i", options: NSString.CompareOptions.caseInsensitive, range: searchRange)
if foundRange.location != NSNotFound {
// found an occurrence of the substring! do stuff here
searchRange.location = foundRange.location + foundRange.length
mutableAttributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.red, range: foundRange)
}
else {
// no more substring to find
break
}
}
//Apply
textLabel.attributedText = mutableAttributedString;
And this output-
This is my solution. Basically, the algorithm traverses the string looking for substring matches and returns those matches in an array.
Since an NSRange is a struct it cannot be added to the array directly. By using NSValue, I can encode the match first and then add it to the array. To retrieve the range, I then decode the NSValue object to an NSRange.
#import <Foundation/Foundation.h>
NSRange makeRangeFromIndex(NSUInteger index, NSUInteger length) {
return NSMakeRange(index, length - index);
}
NSArray<NSValue *> * allLocationsOfStringMatchingSubstring(NSString *text, NSString *pattern) {
NSMutableArray *matchingRanges = [NSMutableArray new];
NSUInteger textLength = text.length;
NSRange match = makeRangeFromIndex(0, textLength);
while(match.location != NSNotFound) {
match = [text rangeOfString:pattern options:0L range:match];
if (match.location != NSNotFound) {
NSValue *value = [NSValue value:&match withObjCType:#encode(NSRange)];
[matchingRanges addObject:value];
match = makeRangeFromIndex(match.location + 1, textLength);
}
}
return [matchingRanges copy];
}
int main(int argc, const char * argv[]) {
#autoreleasepool {
NSString *text = #"TATACCATGGGCCATCATCATCATCATCATCATCATCATCATCACAG";
NSString *pattern = #"CAT";
NSArray<NSValue *> *matches = allLocationsOfStringMatchingSubstring(text, pattern);
NSLog(#"Text: %#", text);
NSLog(#"Pattern: %#", pattern);
NSLog(#"Number of matches found: %li", matches.count);
[matches enumerateObjectsUsingBlock:^(NSValue *obj, NSUInteger idx, BOOL *stop) {
NSRange match;
[obj getValue:&match];
NSLog(#" Match found at index: %li", match.location);
}];
}
return 0;
}
Passing nil to [string rangeOfString:substring options:nil range:searchRange]; shows a warning.
To get rid of the warning, put in an enum from this group
enum {
NSCaseInsensitiveSearch = 1,
NSLiteralSearch = 2,
NSBackwardsSearch = 4,
NSAnchoredSearch = 8,
NSNumericSearch = 64,
NSDiacriticInsensitiveSearch = 128,
NSWidthInsensitiveSearch = 256,
NSForcedOrderingSearch = 512,
NSRegularExpressionSearch = 1024
};
https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/index.html#//apple_ref/doc/constant_group/Search_and_Comparison_Options
Here is a version in Swift 2.2 of PengOne's answer with input from kevinlawler and Gibtang
Note: string and substring are of type NSString
let fullStringLength = (string as String).characters.count
var searchRange = NSMakeRange(0, fullStringLength)
while searchRange.location < fullStringLength {
searchRange.length = fullStringLength - searchRange.location
let foundRange = string.rangeOfString(substring as String, options: .CaseInsensitiveSearch, range: searchRange)
if foundRange.location != NSNotFound {
// found an occurrence of the substring! do stuff here
searchRange.location = foundRange.location + 1
} else {
// no more strings to find
break
}
}
I suggest using regular expression because it's a more declarative way and has fewer lines of code to write.
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"%#" options:nil error:nil];
NSString *toSearchStr = #"12312 %# Text %# asdsa %#";
__block int occurs = 0;
[regex enumerateMatchesInString:toSearchStr options:0 range:NSMakeRange(0, toSearchStr.length) usingBlock:^(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL * _Nonnull stop) {
occurs++;
}];
// occurs == 3

Check IF one String contains the same characters as another string

I am trying to write a function which will allow me to determine whether one NSString* contains the characters of another NSString*. As an example, refer to the below scenario:
NSString *s1 = #"going";
NSString *s2 = #"ievngcogdl";
So essentially when the comparison between these 2 strings occurs, it should return true as the first string s1 has the same characters of the second string s2. Could I use an NSCountedSet? I know that this class has a method containsObject:(id) although I don't think that will solve my problem. Is there any other ways in completing this function and provide me the required results?
I think this method could be rather slow, but I would still favour it over [NSString rangeOfCharacterFromSet:], which requires creating an NSCharacterSet object per comparison:
- (BOOL)string:(NSString *)string containsAllCharactersInString:(NSString *)charString {
NSUInteger stringLen = [string length];
NSUInteger charStringLen = [charString length];
for (NSUInteger i = 0; i < charStringLen; i++) {
unichar c = [charString characterAtIndex:i];
BOOL found = NO;
for (NSUInteger j = 0; j < stringLen && !found; j++)
found = [string characterAtIndex:j] == c;
if (!found)
return NO;
}
return YES;
}
This will work -
-(BOOL) string:(NSString *)string1 containsInputString:(NSString *)string2 {
// Build a set of characters in the string
NSCountedSet *string1Set = [[NSCountedSet alloc]init];
[string1 enumerateSubstringsInRange:NSMakeRange(0, string1.length)
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
[string1Set addObject:substring];
}];
// Now iterated over string 2, removing characters from the counted set as we go
for (int i=0;i<string2.length;i++) {
NSRange range = [string2 rangeOfComposedCharacterSequenceAtIndex:i];
NSString *substring = [string2 substringWithRange:range];
if ([string1Set countForObject:substring]> 0) {
[string1Set removeObject:substring];
}
else {
return NO;
}
}
return YES;
}
Regular Expressions are the best way to check this type of conditions and check this link once
Below I am adding the code for your solution, please check once
NSString *s1 = #"going"
NSString *s2 = #"ievngcogdl";
if ([self string:s1 containsSameCharacterofString:s2]) {
NSLog(#"YES");
}
- (BOOL)string:(NSString *)str containsSameCharacterofString:(NSString *)charString
{
if (charString.length >= str.length) {
NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:#"^[%#]+$", charString] options:NSRegularExpressionCaseInsensitive error:&error];
NSRange textRange = NSMakeRange(0, str.length);
NSRange matchRange = [regex rangeOfFirstMatchInString:str options:NSMatchingReportProgress range:textRange];
return (matchRange.location != NSNotFound);
}
else {
return NO;
}
}
BOOL containsString = [#"Hello" containsString:#"llo"];
if (containsString) {
// Do Stuff
}

Occurance of character after specific index

Is there any way to achieve what JAVA function int indexOf(int ch, int fromIndex) do.
Let me explain it:
NSString *steSample = #"This is sample test string";
Now i want to get the index of i but after 2nd index. How can I achieve this.
Thanks in advance
the regex way is too complicated for me :)
can't we just trim it and then look for it?
that'd be 3 lines...
wrapped in a category:
#import <Foundation/Foundation.h>
#interface NSString (Extend)
-(NSUInteger)indexOfSubstring:(NSString*)needle afterIndex:(NSUInteger)index;
#end
#implementation NSString (Extend)
-(NSUInteger)indexOfSubstring:(NSString*)needle afterIndex:(NSUInteger)index {
id str = [self substringFromIndex:index];
NSUInteger i = [str rangeOfString:needle].location;
return i==NSNotFound ? i : i+index;
}
#end
Demo usage:
int main(int argc, char *argv[]) {
#autoreleasepool {
id str = #"#asd#asd";
NSUInteger index = [str indexOfSubstring:#"#" afterIndex:2];
NSLog(#"index of # is: %d", index);
}
}
I'd do something like this:
NSString *_sample = #"This is sample test string";
NSError *_error;
NSRegularExpression *_regExp = [NSRegularExpression regularExpressionWithPattern:#"i" options:NSRegularExpressionCaseInsensitive error:&_error];
NSArray *_matches = [_regExp matchesInString:_sample options:NSMatchingReportCompletion range:NSMakeRange(0, _sample.length)];
[_matches enumerateObjectsUsingBlock:^(NSTextCheckingResult * result, NSUInteger idx, BOOL *stop) {
if (idx == 0) {
NSLog(#"ignoring first occurance...");
} else {
NSLog(#"occurance's index : %d, character's index in string : %d", idx, result.range.location); // that line is simplified for your problem
}
}];
NOTE: you can rearrange the actual if statement, it currently 'skips' the first occurance and prints the rest – but it can be customised for your further wish.
my console shows something like this:
ignoring first occurance...
occurance's index : 1, character's index in string : 5
occurance's index : 2, character's index in string : 23
NSString *steSample = #"This is sample test string";
NSUInteger count = 0, length = [steSample length];
NSRange range = NSMakeRange(0, length);
while(range.location != NSNotFound)
{
range = [steSample rangeOfString: #"i" options:0 range:range];
if(range.location != NSNotFound)
{
range = NSMakeRange(range.location + range.length, length - (range.location + range.length));
count++;
if (count == 2)
{
NSLog(#"%d", range.location); // print 6 which is location of second 'i'
}
}
}

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....

Finding word in NSString and checking before and after character this word?

How to find word in NSString and check characters before and after this word?
"This pattern has two parts separated by the"
How to find tern and how to check the character before and after
Before word character:"t"
After word character:" "
You can use NSScanner to get indexes of these two characters.
Example:
NSString *string = #"tern";
NSScanner *scanner = [[NSScanner alloc] initWithString:#"This pattern has two parts separated by the"];
[scanner scanUpToString:string intoString:nil];
NSUInteger indexOfChar1 = scanner.scanLocation - 1;
NSUInteger indexOfChar2 = scanner.scanLocation + string.length;
You can also use a rangeOfString method:
Example:
NSRange range = [sourceString rangeOfString:stringToLookFor];
NSUInteger indexOfChar1 = range.location - 1;
NSUInteger indexOfChar2 = range.location +range.length + 1;
Then, when you have indexes, getting the characters is easy:
NSString *firstCharacter = [sourceString substringWithRange:NSMakeRange(indexOfChar1, 1)];
NSString *secondCharacter = [sourceString substringWithRange:NSMakeRange(indexOfChar2, 1)];
Hope this helps.
Here is an implementation using Regular Expressions
NSString *testString= #"This pattern has two parts separated by the";
NSString *regexString = #"(.)(tern)(.)";
NSRegularExpression* exp = [NSRegularExpression
regularExpressionWithPattern:regexString
options:NSRegularExpressionSearch error:&error];
if (error) {
NSLog(#"%#", error);
} else {
NSTextCheckingResult* result = [exp firstMatchInString:testString options:0 range:NSMakeRange(0, [testString length] ) ];
if (result) {
NSRange groupOne = [result rangeAtIndex:1]; // 0 is the WHOLE string.
NSRange groupTwo = [result rangeAtIndex:2];
NSRange groupThree = [result rangeAtIndex:3];
NSLog(#"[%#][%#][%#]",
[testString substringWithRange:groupOne],
[testString substringWithRange:groupTwo],
[testString substringWithRange:groupThree] );
}
}
Results:
[t][tern][ ]
Its better to get pre and post character in NSString to avoid handling of unicode characters.
NSString * testString = #"This pattern has two parts separated by the";
NSString * preString;
NSString * postString;
NSUInteger maxRange;
NSRange range = [testString rangeOfString:#"tern"];
if(range.location == NSNotFound){
NSLog(#"Not found");
return;
}
if (range.location==0) {
preString=nil;
}
else{
preString = [testString substringWithRange:NSMakeRange(range.location-1,1)];
}
maxRange = NSMaxRange(range);
if ( maxRange >=testString.length ) {
postString = nil;
}
else{
postString = [testString substringWithRange:NSMakeRange(range.location+range.length, 1)];
}

Resources