Using the shouldChangeTextInRange delegate to detect certain phrases when typed in a UITextViewgives different range and text values when alternating between the stock keyboard and custom keyboards like SwiftKey (in identical scenarios).
-(BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {...}
For example:
The moment after I type: "ABCDEFG HIJ " without quotes and including the ending space using the stock keyboard, I find the range is equal to (11, 0). However, with SwiftKey, I'm getting a range of (10, 1). Additionally, the text will be equal to just the ending space using the stock keyboard, but will be equal to 'J' plus the space using SwiftKey. Any ideas why this is happening and how I can standardize my function's behavior given these differences if I want to properly detect certain phrases? (my goal is detecting the two words immediately before every space press)
EDIT: this is how I detect the two words in front of a space. My issue is that range is giving me different values when I use a non-stock keyboard
NSUInteger charsBackUntilSecondSpace = 0;
NSUInteger numSpacesFound = 0;
for (int i = (int) range.location - 1; i>= 0; i--) {
if([textView.text characterAtIndex:i] == ' ') {
numSpacesFound++;
}
if (numSpacesFound == 2) {
break;
}
else {
charsBackUntilSecondSpace++;
}
}
UITextRange *selectedTextRange = textView.selectedTextRange;
NSUInteger location = [textView offsetFromPosition:textView.beginningOfDocument
toPosition:selectedTextRange.start];
NSRange inFrontOfCursorRange = NSMakeRange (location - charsBackUntilSecondSpace, charsBackUntilSecondSpace);
my goal is detecting the two words immediately before every space
press
You can achieve it like that below:-
NSInteger lenPos=(range.length > 2) ? 2 : range.length;
NSString *txt=[textView.text substringWithRange:NSMakeRange(range.length-lenPos-1, lenPos)] ;
Related
I'm trying to make a fb messenger style new message formation uitextview.. as shown in the image..
Specifically, how to make the uitextview which is at the top of the tableview (showing list of selected friends). I want to make a uitextview with the following properties..
1) It expands/contracts as more names are added/removed to/from it.
2) The textview is editable - but not partially editable, i.e., a name is either wiped out by backspace or not (like how it happens in fb)
3) Possibly this editing happens in nice aesthetics (similar to fb, make the color of the entire text blue colored before backspacing it out)
It has been a while since I have used objective-c, but I believe this is correct. It may not be though. And yes I would eventually switch to swift.
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
//you should have an array of names as a property, not here.
NSMutableArray* nameArray = [#"sam"];
if ([text isEqual: #""]) {// this is delete
for (NSString *name in nameArray) {
NSRange nameRange = [textView.text rangeOfString:name];
if (nameRange.location == range.location) {
textView.text = [textView.text stringByReplacingCharactersInRange:nameRange withString:#""];
[nameArray removeObject:name]
return false;
}
}
}
return true;
}
I'm having the following problem, and am not sure if this is an iOS bug, or I'm misunderstanding UITextViewDelegate callbacks. I'm able to reproduce this behavior on my device (iPad retina) and on the simulator only if I use the software keyboard (i.e. use my mouse to click the on-screen keyboard).
Consider the following contrived example project (Updated 1/9/14):
https://www.dropbox.com/s/1q3vqfnsmmbhnuj/AutocorrectBug.zip
It's a simple UITextView, with a view controller set as its delegate, with the following delegate method:
- (BOOL) textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if ([text isEqualToString:#" "]
&& range.length == 0
&& (range.location == 0
|| [[textView.text substringWithRange:NSMakeRange(range.location-1, 1)] isEqualToString:#"\n"])) {
textView.backgroundColor = [UIColor yellowColor];
return NO;
}
return YES;
}
My intention with this code is that when the user types a space (edit: and that space is the first character on the line), the space is ignored and instead some other functionality happens, in this case the UITextView turns yellow.
Now consider these steps:
Tap in the text view to make it first responder
Type "Apple", hit return and note the positioning
Now, type a space (turning the text view yellow), type "Apple" again and hit return.
Expected: A yellow background and text reading:
Apple
Apple
Observed: A yellow background and text reading (due to an autocorrection):
Apple
Apple
It appears the autocorrection logic is ignoring the result of textView:shouldChangeTextInRange:replacementText:.
Is this expected behavior?
If so can it be worked around?
Edit 1/9/14: only the first space on a line should be ignored, further spaces should be processed as normal (i.e. inserted into the text), ruling out a brute force strip of spaces. Also I'm dealing with a large string (potentially hundreds of thousands of characters) in a text editor (meaning constant user typing), so analyzing the entire string with every keystroke isn't going to be performant.
Yes, I see it too on my iPad 7.0.3 simulator.
One way to solve it is to add this UITextViewDelegate method:
- (void)textViewDidChange:(UITextView *)textView
{
// eliminates spaces, including those introduced by autocorrect
if ([textView.text rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]].location != NSNotFound) {
textView.text = [textView.text stringByReplacingOccurrencesOfString:#" " withString:#""];
}
}
Update
It seems that this delegate method is called after autocorrection, just that the in the case you want to prevent (where "Apple" becomes " Apple") the replacement text is " Apple" and not " ". So to modify the implementation to prevent "\n " but allow other " " characters in your text, you could try comparing the first character of text to " " instead of comparing text to " ".
- (BOOL) textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
// compare first character to 'space' character by decimal value
if (text.length && [text characterAtIndex:0] == 32) {
// check if previous character is a newline
NSInteger locationOfPreviousCharacter = range.location - 1;
if (locationOfPreviousCharacter < 0 || [textView.text characterAtIndex:locationOfPreviousCharacter] == 10) {
textView.backgroundColor = [UIColor yellowColor];
return NO;
}
}
return YES;
}
I am trying to restrict the user to enter max 50 words in UITextView. I tried solution by PengOne from this question1 . This works for me except user can enter unlimited chars in the last word.
So I thought of using regular expression. I am using the regular expression given by
VonC in this question2. But this does not allow me enter special symbols like , " # in the text view.
In my app , user can enter anything in the UITextView or just copy-paste from web page , notes , email etc.
Can anybody know any alternate solution for this ?
Thanks in advance.
This code should work for you.
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
return textView.text.length + (text.length - range.length) <= 50;
}
Do as suggested in "question2", but add # within the brackets so that you can enter that as well. May need to escape it if anything treats it as a special character.
You use [NSCharacterSet whitespaceCharacterSet] to calculate word.
- (BOOL) textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
static const NSUInteger MAX_NUMBER_OF_LINES_ALLOWED = 3;
NSMutableString *t = [NSMutableString stringWithString: self.textView.text];
[t replaceCharactersInRange: range withString: text];
NSUInteger numberOfLines = 0;
for (NSUInteger i = 0; i < t.length; i++) {
if ([[NSCharacterSet whitespaceCharacterSet] characterIsMember: [t characterAtIndex: i]]) {
numberOfWord++;
}
}
return (numberOfWord < 50);
}
The method textViewDidChangeSelection: is called when a section of text is selected or the selection is changed, such as when copying or pasting a section of text.
I have plain NSString which i put into UITextView . I need to search position when new line starts. I think it can be done if textview converts string from
#"This is a big test string which i want to put into UITextView" to #"This is a\n big test \nstring which\n i want to\n put into\n UITextView" automaticaly. Then i can search for "\n" and find position of some line . Any idea how can i do it ?
That's not the way how UITextView works. The process of laying out text in a container is more complicated than just finding line breaks and does not produce a new string containing NL characters.
On iOS 7 you could use the TextKit properties (layoutManager, textContainer, ...) of UITextView to access the underlying layout components and query them for positions of parts of your string.
If you have to support older iOS versions there's characterRangeByExtendingPosition:inDirection: in the UITextInput protocol. You can use it to calculate the extent of a line of text.
I count "\n"s in an app to know when to hide the keyboard. instead of counting \ns you can count the other chars to get your "\n" location. Rows just counts the number of "\n"s, and is reset to zero on view did load.
// dismiss keyboard from text view with return key
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
if([text isEqualToString:#"\n"]) {
rows++;
if ( rows >= 2 ){
// resign text view
[self.quotationTextView resignFirstResponder];
return NO;
}else{
return YES;
}
}else{
rows = 0;
}
return YES;
}
I'm developing a code in where I need to perform a delete and backspace from UITextView.
I tried:
-(IBAction)delete:(id)sender{
uitextview.text =[uitextview.text substringToIndex:[uitextview.text length]-1];
}
this perform back space and remove last character but how to delete a character from specific location in uitextview on a particular cursor location.
Please help me out.
Use the selectedRange property of the UITextView. This indicates the cursor position and/or selected text (the length will be zero if no text is selected).
So, if the range.length is > 0, you can use stringByReplacingCharactersInRange:withString: to delete the selected text.
Otherwise, create a new range with location one less than your selected range and length of 1, and then remove this character.
You may need to re-set the selectedRange property to make sure the cursor ends up in the appropriate position. I imagine all this is done automatically when using the standard keyboard.
Thanks for ur suggest.I want to suggest one way to perform this work.
NSRange range = uitextview.selectedRange;
NSRange selectedRange = uitextview.selectedRange;
NSLog(#"%d",selectedRange.length);
NSLog(#"%d",selectedRange.location);
int temploc=selectedRange.location;
NSMutableString *text = [uitextview.text mutableCopy];
// NSLog(#"Length: %d Location: %d", range.length, range.location);
if (range.length > 0) {
[text deleteCharactersInRange:range];
}
if (range.length == 0 && range.location != 0) {
NSRange backward = NSMakeRange(range.location - 1, 1);
// NSLog(#"Length: %d Location: %d", backward.length, backward.location);
[text deleteCharactersInRange:backward];
}
uitextview.text = text;