How can I get the current selected line number inside UITextView? - ios

I am using the below written method for getting the current selected line number of textView having attributed text. It works fine if last line is not blank. If last line goes blank this method is not giving a proper line number.
Method :
- (NSInteger)getCurrentLineIndex{
/*
Get Current Line Inside UITextView
*/
CGPoint cursorPosition = [self caretRectForPosition:self.selectedTextRange.start].origin;
return (NSInteger)(cursorPosition.y / self.font.lineHeight);
}
Is there any other way to get the current line index?
Fixed : I already had mRangesArray in which each object gave a range of each line. Using those range objects and comparing with the selected range, I was able to fix the problem.
1) Get Ranges Array
- (void)analyse
{
if (self.mRangesArray != nil) {
[self.mRangesArray removeAllObjects];
}
NSString *string = self.text;
NSUInteger numberOfLines, index, stringLength = [string length];
NSMutableArray *ranges = [NSMutableArray new];
for (index = 0, numberOfLines = 0; index < stringLength; numberOfLines++)
{
NSRange range = [string lineRangeForRange:NSMakeRange(index, 0)];
[ranges addObject:[NSValue valueWithRange:range]];
}
if (self.mRangesArray == nil) {
self.mRangesArray = [[NSMutableArray alloc]init];
}
self.mRangesArray = ranges;
}
2) Compare Range Objects inside the RangesArray with the current selected Range
- (NSInteger)getCurrentLineIndex{
/*
This method will be used to get mRangesArray
*/
[self analyse];
/*
Get Current Line Inside UITextView
*/
NSRange selectedRange = self.selectedRange;
__block NSInteger index = [self.mRangesArray count]-1;
[self.mRangesArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSRange objectRange = [obj rangeValue];
if (*stop == NO) {
if (NSLocationInRange(selectedRange.location, objectRange)) {
index = idx;
*stop =YES;
return;
}
}
}];
}
Thanks guys for your help. Cheers :)

EDIT: I've obviously ignored the currently selected part of your question.
I'll still keep the code here in case someone needs it.
Have you tried counting the newline separator?
let str = "First\nSecond\nThird"
let tok = str.componentsSeparatedByString("\n")
print(tok.count) // Hopefully prints "3"

Try this code it works
UIFont * fontNAme = [UIFont boldSystemFontOfSize:13.0];
CGSize size = [textView.text fontNAme constrainedToSize:textView.frame.size lineBreakMode:UILineBreakModeWordWrap];
int lineNumber = size.height / fontNAme .lineHeight;
NSLog(#"line = %d", lineNumber);

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.

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

Delete Button Calculator *Help*

I have this code for my calculator that lets me delete one number at a time!
- (IBAction)deleteButton:(id)sender {
NSString *string = [Screen text];
int length = [string length];
NSString *temp = [string substringToIndex:length-1];
if ([temp length] == 0) {
temp = #"0";
}
[Screen setText:temp];
}
It works, but whenever I enter another number, it resets back to the whole thing, so lets take this as an example.
I have the number 5678, (I deleted 678), So my new number is 5, (Now if I press another number), it goes back to 56781 ( 1 being the new number)
Heres my full code for my project! ---> http://txt.do/oduh
As seen in your code, you are using SelectNumber to store value everywhere, but in deleteButton method you are not storing new value in SelectNumber. So you need to set the new value in deleteButton.
- (IBAction)deleteButton:(id)sender {
NSString *string = [Screen text];
int length = [string length];
NSString *temp = [string substringToIndex:length-1];
if ([temp length] == 0) {
temp = #"0";
}
SelectNumber = [temp intValue]; // set new value here
[Screen setText:temp];
}

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

Resources