User input in UITextView call delegate method twice? - iphone

I want to compare a string with user input character by character. e.g. I want to let user input "I have an apple." and compare the input with this string to see if his input is correct. When he input a wrong character, iphone will vibrate to inform him immediately. The problem is that I find some characters like the space will call the delegate method twice- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
When I press the space key, the first time I compare the text with the ' ', the result will show me that they're the same character. But after that, I have to advance the index of string character to the next. And the second time the delegate method is called, iphone will vibrate. Any ideas about how to solve this problem?
Here is my code:
strText = #"I have an apple.";
index = 0;
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
NSRange rg = {index, 1};
NSString *correctChar = [strText substringWithRange:rg];
if([text isEqualToString:correctChar])
{
index++;
if(index == [strText length])
{
// inform the user that all of his input is correct
}
else
{
// tell the user that he has index(the number of correct characters) characters correct
}
}
else {
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
return NO;
}
return YES;
}

try this
- (void)textViewDidChange:(UITextView *)textView{
if(![myStringToCompareWith hasPrefix:textView.text]){
//call vibrate here
}
}

Building on Morion's suggestion of using hasPrefix:, I think this is the solution that you are looking for:
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
// create final version of textView after the current text has been inserted
NSMutableString *updatedText = [NSMutableString stringWithString:textView.text];
[updatedText insertString:text atIndex:range.location];
if(![strTxt hasPrefix:updatedText]){
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
return NO;
}
return YES;
}

Related

Disable return key in UITextView

I am trying to disable the return key found when typing in a UITextView. I want the text to have no page indents like found in a UITextField. This is the code I have so far:
- (BOOL)textView:(UITextView *)aTextView shouldChangeTextInRange:(NSRange)aRange replacementText:(NSString*)aText
{
if ([aTextView.text isEqualToString:#"\r\n"]) {
return NO;
}
Any ideas?
Try to use like this..
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:#"\n"])
{
[textView resignFirstResponder];
return NO;
}
return YES;
}
Another solution without magic hardcoded strings will be:
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText: (NSString *)text {
if( [text rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet]].location == NSNotFound ) {
return YES;
}
return NO;
}
In Swift 3+ add:
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
guard text.rangeOfCharacter(from: CharacterSet.newlines) == nil else {
// textView.resignFirstResponder() // uncomment this to close the keyboard when return key is pressed
return false
}
return true
}
Don't forget to add textView's delegate in order for this to be called
I know this is late reply but this code is perfect working for me.
This will disable return key initially until type any character.
textfield.enablesReturnKeyAutomatically = YES;
By Interface Builder set this property,
why dont you change UITextView return key type like this?

How do I limit characters in UITextView ?

I have been looking for solutions and found the following piece of code. But I do not know how to use it, unfortunately.
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)string {
NSUInteger newLength = [textField.text length] + [string length] - range.length;
return (newLength > 25) ? NO : YES;
}
Just for testing purposes I set up an IBACTION
-(IBAction)checkIfCorrectLength:(id)sender{
[self textView:myTextView shouldChangeTextInRange: ?? replacementText: ?? ];
}
What do I pass for shouldChangeTextInRange and replacementText ?
Or am I getting it completely wrong ?
Calling textView:shouldChangeTextInRange:replacementText: from checkIfCorrectLength: doesn't make sense. If you want to test the length from multiple methods, factor the test out into its own method:
- (BOOL)isAcceptableTextLength:(NSUInteger)length {
return length <= 25;
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)string {
return [self isAcceptableTextLength:textField.text.length + string.length - range.length];
}
-(IBAction)checkIfCorrectLength:(id)sender{
if (![self isAcceptableTextLength:self.textField.text.length]) {
// do something to make text shorter
}
}
Hi I found and modified the code here. So for xamarin users. try the following:
textView.ShouldChangeText += delegate
{
if(textView.Text.Length > 159) // limit to one sms length
{
return false;
}
return true;
}
You don't call this method yourself, the text view calls it whenever it's about to change its text. Just set the text view's delegate property (e.g. to your view controller) and implement the method there.
If the current object is the delegate of the text view, then you can use the following snippet:
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
return weightTextView.text.length + text.length - range.length < 7;
}
This worked for me.
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
if([text length] == 0)
{
if([textView.text length] != 0)
{
return YES;
}
else {
return NO;
}
}
else if([[textView text] length] > your limit value )
{
return NO;
}
return YES;
}

Keyboard is not responding after implementing UISearchBar delegate method

I have an UISearchBar which i implemented in my viewDidLoad: by code.
I have also set the UISearchBarDelegate.
Now i want to restrict the user from entering more than 5 chracter So i implement this delegate method
- (BOOL)searchBar:(UISearchBar *)searchBar shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
NSLog(#"shouldChangeTextInRange");
if (searchBar.text.length >= 5)
return NO;
return YES;
}
Its working fine.
The problem is when i typed upto 5 chracters & try to use the keyboard Backspace character, it is not working.
Also now if i pressed Search button in keyboard the searchBarSearchButtonClicked: is not getting called.
I am currently using
XCode version :3.2.5
iOS SDK :4.2
You should do your test on the new text length (then length of the text that you will have if the suggested text change is applied), not the actual text length.
For that, you first need to compute the new text :
- (BOOL)searchBar:(UISearchBar *)searchBar shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
if ([text isEqualToString:#"\n"])
return YES; // accept validation button
NSString* newText = [searchBar.text stringByReplacingCharactersInRange:range withString:text];
if (newText.length >= 5)
return NO;
return YES;
}
You can't use backspace because of your code. after typing 5 characters, your searchBar is stuck. Use it instead :
if ([textField.text length] + [string length] - range.length > 5) {
return NO;
}

IPhone : How can I prevent inserting text if the text exceeds the size of a UITextview

I have a UITextView, the size of which is set through setFrame:CGRectMake(30, 100, 273, 140).
I would like to prevent inserting text that exceeds the size of the TextView.
So I've tried :
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if ((textView.contentOffset.y + textView.frame.size.height) < textView.contentSize.height){
return NO;
}
else {
return YES;
}
return YES;
}
The pb is that when the condition is met, I can't use backspace to erase the extra text. The textview is no longer editable. What am I doing wrong here ?
Thanks for your help.
Have you tried something similar to this? I wrote it real quick and haven't tested it out. Let me know if it works. I don't know off the top of my head anything about the fonts, so you'll have to do some reading and adjust the code accordingly, where it says whateveryourfontis
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
NSSTring *newString = [NSString stringWithFormat:#"%#%#", textView.text, text];
CGSize *newSize = [newString sizeWithFont: [UIFont _whateveryourfontis_] constrainedToSize:textView.frame.size];
return (newSize.height > textView.frame.size.height) ? NO : YES;
}
Ok, I think I've found out by myself this time.
I don't know if it is the best solution but it works fine at least.
- (void)textViewDidChange:(UITextView *)uiViewContent{
if ((uiViewContent.contentOffset.y + uiViewContent.frame.size.height) < self.entryTextField2.contentSize.height){
self.entryTextField2.text=[self.entryTextField2.text substringToIndex:[self.entryTextField2.text length] - 1];
}
}
Hope it may help someone...
Mike

Limiting pasted string length in UITextView or UITextField

The problem of limiting strings that are directly entered into a UITextView or UITextField has been addressed on SO before:
iPhone SDK: Set Max Character length TextField
iPhone sdk 3.0 issue
However now with OS 3.0 copy-and-paste becomes an issue, as the solutions in the above SO questions don’t prevent pasting additional characters (i.e. you cannot type more than 10 characters into a field that is configured with the above solutions but you can easily paste 100 characters into the same field).
Is there a means of preventing directly entered string and pasted string overflow?
I was able to restrict entered and pasted text by conforming to the textViewDidChange: method within the UITextViewDelegate protocol.
- (void)textViewDidChange:(UITextView *)textView
{
if (textView.text.length >= 10)
{
textView.text = [textView.text substringToIndex:10];
}
}
But I still consider this kind of an ugly hack, and it seems Apple should have provided some kind of "maxLength" property of UITextFields and UITextViews.
If anyone is aware of a better solution, please do tell.
In my experience just implementing the delegate method:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
works with pasting. The entire pasted string comes across in the replacementString: argument. Just check it's length, and if it's longer than your max length, then just return NO from this delegate method. This causes nothing to be pasted. Alternatively you could substring it like the earlier answer suggested, but this works to prevent the paste at all if it's too long, if that's what you want.
Changing the text after it's inserted in textViewDidChange: causes the app to crash if the user presses 'Undo' after the paste.
I played around for quite a bit and was able to get a working solution. Basically the logic is, do not allow the paste if the total length is greater than the max characters, detect the amount that is overflown and insert only the partial string.
Using this solution your pasteboard and undo manager will work as expected.
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
NSInteger newLength = textView.text.length - range.length + text.length;
if (newLength > MAX_LENGTH) {
NSInteger overflow = newLength - MAX_LENGTH;
dispatch_async(dispatch_get_main_queue(), ^{
UITextPosition *start = [textView positionFromPosition:nil offset:range.location];
UITextPosition *end = [textView positionFromPosition:nil offset:NSMaxRange(range)];
UITextRange *textRange = [textView textRangeFromPosition:start toPosition:end];
[textView replaceRange:textRange withText:[text substringToIndex:text.length - overflow]];
});
return NO;
}
return YES;
}
This code won't let user to input more characters than maxCharacters.
Paste command will do nothing, if pasted text will exceed this limit.
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
let newText = (textView.text as NSString).replacingCharacters(in: range, with: text)
return newText.count <= maxCharacters;
}
One of the answers in the first question you linked above to should work, namely using something like
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(limitTextField:) name:#"UITextFieldTextDidChangeNotification" object:myTextField];
to watch for changes to the text in the UITextField and shorten it when appropriate.
Also, string length as in '[string length]' is one thing, but one often needs to truncate to a byte count in a certain encoding. I needed to truncate typing and pasting into a UITextView to a max UTF8 count, here's how I did it. (Doing something similar for UITextField is an exercise to the reader.)
NSString+TruncateUTF8.h
#import <Foundation/Foundation.h>
#interface NSString (TruncateUTF8)
- (NSString *)stringTruncatedToMaxUTF8ByteCount:(NSUInteger)maxCount;
#end
NSString+TruncateUTF8.m
#import "NSString+TruncateUTF8.h"
#implementation NSString (TruncateUTF8)
- (NSString *)stringTruncatedToMaxUTF8ByteCount:(NSUInteger)maxCount {
NSRange truncatedRange = (NSRange){0, MIN(maxCount, self.length)};
NSInteger byteCount;
// subtract from this range to account for the difference between NSString's
// length and the string byte count in utf8 encoding
do {
NSString *truncatedText = [self substringWithRange:truncatedRange];
byteCount = [truncatedText lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
if (byteCount > maxCount) {
// what do we subtract from the length to account for this excess count?
// not the count itself, because the length isn't in bytes but utf16 units
// one of which might correspond to 4 utf8 bytes (i think)
NSUInteger excess = byteCount - maxCount;
truncatedRange.length -= ceil(excess / 4.0);
continue;
}
} while (byteCount > maxCount);
// subtract more from this range so it ends at a grapheme cluster boundary
for (; truncatedRange.length > 0; truncatedRange.length -= 1) {
NSRange revisedRange = [self rangeOfComposedCharacterSequencesForRange:truncatedRange];
if (revisedRange.length == truncatedRange.length)
break;
}
return (truncatedRange.length < self.length) ? [self substringWithRange:truncatedRange] : self;
}
#end
// tested using:
// NSString *utf8TestString = #"Hello world, Καλημέρα κόσμε, コンニチハ ∀x∈ℝ ıntəˈnæʃənəl ⌷←⍳→⍴∆∇⊃‾⍎⍕⌈ STARGΛ̊TE γνωρίζω გთხოვთ Зарегистрируйтесь ๏ แผ่นดินฮั่นเสื่อมโทรมแสนสังเวช ሰማይ አይታረስ ንጉሥ አይከሰስ። ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ⡌⠁⠧⠑ ⠼⠁⠒ ⡍⠜⠇⠑⠹⠰⠎ ⡣⠕⠌ ░░▒▒▓▓██ ▁▂▃▄▅▆▇█";
// NSString *truncatedString;
// NSUInteger byteCount = [utf8TestString lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
// NSLog(#"length %d: %p %#", (int)byteCount, utf8TestString, utf8TestString);
// for (; byteCount > 0; --byteCount) {
// truncatedString = [utf8TestString stringTruncatedToMaxUTF8ByteCount:byteCount];
// NSLog(#"truncate to length %d: %p %# (%d)", (int)byteCount, truncatedString, truncatedString, (int)[truncatedString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
// }
MyViewController.m
#import "NSString+TruncateUTF8.h"
...
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)replacementText
{
NSMutableString *newText = textView.text.mutableCopy;
[newText replaceCharactersInRange:range withString:replacementText];
// if making string larger then potentially reject
NSUInteger replacementTextLength = replacementText.length;
if (self.maxByteCount > 0 && replacementTextLength > range.length) {
// reject if too long and adding just 1 character
if (replacementTextLength == 1 && [newText lengthOfBytesUsingEncoding:NSUTF8StringEncoding] > self.maxByteCount) {
return NO;
}
// if adding multiple charaters, ie. pasting, don't reject altogether but instead return YES
// to accept and truncate immediately after, see http://stackoverflow.com/a/23155325/592739
if (replacementTextLength > 1) {
NSString *truncatedText = [newText stringTruncatedToMaxUTF8ByteCount:self.maxByteCount]; // returns same string if truncation needed
if (truncatedText != newText) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0LL), dispatch_get_main_queue(), ^{
UITextPosition *replaceStart = [textView positionFromPosition:textView.beginningOfDocument offset:range.location];
UITextRange *textRange = [textView textRangeFromPosition:replaceStart toPosition:textView.endOfDocument];
[textView replaceRange:textRange withText:[truncatedText substringFromIndex:range.location]];
self.rowDescriptor.value = (truncatedText.length > 0) ? truncatedText : nil;
});
}
}
}
[self updatedFieldWithString:(newText.length > 0) ? newText : nil]; // my method
return YES;
}
You can know the pasted string if you check for string.length in shouldChangeCharactersIn range: delegate method
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string.length > 1 {
//pasted string
// do you stuff like trim
} else {
//typed string
}
return true
}
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
if(string.length>10){
return NO;
}
return YES;
}