When using a UITextView to gather user input in Objective-C, how can I limit the user from trying to do more than one line break at a time?
So, this would be fine:
This is my text.
Here is some more text.
But this would not be fine:
This is my text.
Here is some more text way down here.
In your ViewController.h add UITextViewDelegate:
YourViewController : UIViewController <UITextViewDelegate>
and in the ViewController.m implement the method textView:shouldChangeTextInRange:replacementText: this way:
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if ([text isEqualToString:#"\n"]) {
NSMutableString *futureString = [textView.text mutableCopy];
[futureString insertString:text atIndex:range.location];
NSRange rangeOflineBreaks = [futureString rangeOfString:#"\n\n\n"];
if (rangeOflineBreaks.location != NSNotFound) {
return NO;
}
}
return YES;
}
This will be executed every time before the user wants to add some text to the textView and wont let him add another line break if it notices that he wants to add a line break and it finds, after trying adding it (that's why the futureString name) a triple line break mode. In that case he wont let the user add another line break.
Try it out, it should work :)
PS: Don't forget to set your textView delegate the viewController in the viewDidLoad (yourTextView.delegate = self)
Related
I have a if/else statement in my objective-c code. The if/else statement runs like this:
-(void)textViewDidBeginEditing:(UITextView *)textView
{
if(textView==self.heardTextView)
{
NSString *string = textView.text;
if ([string rangeOfString:#"CLOSER"].location == NSNotFound)
{
NSLog(#"closest");
}
}
}
The premise of the if/else statement is - if the textview equals a certain word some code will run. But this code isn't running.
I have put a breakpoint on my code and a NSLOG and nothing.
check your text field delegate if you not set your delegate then it's not called..
In your Viewcontroller.H
select your textview give delegate on textview.
First you need to set the delegate of the text view. You can do this with Interface Builder, but if you prefer using code you can do this in the viewDidLoad method of your view controller:
myTextView.delegate = self;
Now, when do you want the NSLog statement to be executed? Right now it looks like it will be run only if you type "CLOSER", then make the text view lose focus and then click on it again.
If you want the NSLog to run as you type, you should use:
-(BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
And also be careful, you need to calculate the next string value in the text field before testing its contents:
NSString *finalText = [textField.text stringByReplacingCharactersInRange:range withString:string];
Check your delegates, you must not have made the delegate of your textview as self. Check it once again. Or you might have done to some other class.
I have a UITextView which I am using as text entry in a chat application. When the user presses return then I want to do some action e.g. save the chat message.
I haven't been able to find a solution that allows me to do this (lots for TextFields but not for TextView).
Here is the solution I am trying at the moment, which seems to be the most obvious I can find, but it isnt working, in debug I see that the method isn't touched:
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if ([text isEqualToString:#"\n"]) {
NSLog(#"Return pressed");
} else {
NSLog(#"Other pressed");
}
return YES;
}
In my chat .h file:
#interface ChatTableViewController : UITableViewController <UITextViewDelegate>
and viewdidload .m file
- (void)viewDidLoad {
[super viewDidLoad];
enterText.delegate = self;
// more
}
Incidentally if there is a better field to use as input in a chat program than UITextView please let me know.
Many thanks
... but it isn't working, in debug I see that the method isn't touched
Your problem obviously is that the delegate method is not called. Fix the delegate and use the code you already have: it's good.
Try this way. No need to go with delegates.
Add this event responder at where you initialize the textView.
[theTextView addTarget:self
action:#selector(targetMethodToPerformCustomOperation)
forControlEvents:UIControlEventEditingDidEndOnExit];
I am trying to achieve the same effect as Facebook's new status field: place something next to the last character which cannot be modified or selected by the user (for example, a tag: "with Joh Doe").
What's the best way of achieving it?
Thanks
Nick
An easier solution would be to simply make the last n characters in the TextView not editable. Then let the TextView handle moving and wrapping the text as needed. I would guess that is what Facebook is doing, just with some attributed text at the end.
In your ViewControllers.h, make sure the viewController conforms to the UITextViewDelegate as follows.
#interface ViewController : UIViewController <UITextViewDelegate>
Then in the viewDidLoad: method, set the delegate for the UITextView to the ViewController. You can also add your fixed text.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.myTextView.delegate = self;
self.myTextView.text = " posted by Mike";
}
Then, we will use the shouldChangeCharactersInRange: method to prevent the user from editing the text at the end you want to preserve.
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
BOOL deleteKeyPressed = ([text length] == 0 && range.length > 0);
if( range.location > (textView.text.length - #" - posted by Mike".length) || (deleteKeyPressed && range.location > (textView.text.length - #" - posted by Mike".length)) )
{
return NO;
}
return YES;
}
I think this should get you started and make you life easier than finding the end of the last text, then putting another view at the end that may need to wrap if there is not enough room to fit it.
So to prevent the user from selecting the "reserved" text, add the following delegate method to your ViewController's .m:
-(void) textViewDidChangeSelection:(UITextView *)textView
{
if( textView.selectedRange.location > (textView.text.length - #" - posted by Mike".length) )
{
[textView setSelectedRange:NSMakeRange((textView.text.length - #" - posted by Mike".length), 0 )];
}
}
You'll still need to handle if the user selects all the text, and chooses to "cut" or "paste", but that should just be another special case in shouldChangeTextInRange. I'll let you figure that one out - shouldn't be hard. Certainly a lot easier than trying to dynamically place, size, and wrap a TextView within another TextView.
Thanks in advance for your help. I try to boost my learning curve of Objective-c and defy myself with a lot of cases.
I try to do a simple app which simulates the comportment of a terminal session:
First step: a prompt is waiting and I enter a first command: eg. date. Then I get a result. Second: a prompt is waiting again below the result. Then I give a second command: time
etc.
I did a lot of tests with an UItextField to input different texts and commands, and a UITextView to display the results. I also use an NSMutable Array to stock all inputs/results. Nothing work very well. I would like to get your advice on that matter and that you point me the best approach or a code source to learn to reproduce a terminal gui. Is an array a good solution, how to place the textField at the end of the textView, etc.? Thanks+
This is just a general approach of what you want to achieve.
Use a single UITextView for input and output.
At first, add a simple character to your UITextView, for example ">", so the user starts typing after this character.
Implement this UITextView delegate method to listen on when the user taps "return" :
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:#"\n"]) {
// Handle what happens when user presses return
return NO;
}
return YES;
}
What I would do here is when the user presses return, get the whole UITextField's content, and use something like NSArray *stringArray = [_textField.text componentsSeparatedByString: #">"];. That way, the last element of your array is the last command the user entered. Then, test the command and append the appropriate answer to the UITextView. Don't forget to add #"\n>" after it so you prompt the user a new command line.
What's left to do here is prevent the user from erasing your ">".
It's an idea, there's probably many other ways to do it. Comment if you need more details on something !
SPOILER ALERT : full code
In my storyboard, I simply have a UITextView linked to ViewController.h, with the name textView. Note that the following code does not handle the user removing text from the UITextView. You can test the code by typing "hello" in the console.
ViewController.m :
#import "ViewController.h"
#interface ViewController () {
// Store supported commands and outputs
NSDictionary *commands;
}
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Initialize textView
_textView.text = #">";
[_textView becomeFirstResponder];
// Init supported commands with associated output
commands = [NSDictionary dictionaryWithObjectsAndKeys:#"Hello World !", #"hello", nil];
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
// Deleting something
if([text isEqualToString:#""]) {
UITextPosition *beginning = textView.beginningOfDocument;
UITextPosition *start = [textView positionFromPosition:beginning offset:range.location];
UITextPosition *end = [textView positionFromPosition:start offset:range.length];
UITextRange *textRange = [textView textRangeFromPosition:start toPosition:end];
NSString *textToReplace = [textView textInRange:textRange];
NSLog(#"%#", textToReplace);
if ([textToReplace isEqualToString:#">"]) return NO;
return YES;
}
if([text isEqualToString:#"\n"]) {
// Handle what happens when user presses return
NSArray *stringArray = [_textView.text componentsSeparatedByString: #">"];
NSLog(#"Last command : %#", [stringArray lastObject]);
[self handleCommand:[stringArray lastObject]];
return NO;
}
return YES;
}
-(void)handleCommand:(NSString*)command {
NSString *output = [commands objectForKey:command];
// If an unsupported command was typed
if (output == nil) {
output = #"Unknown command";
}
// Write output to the textView
_textView.text = [_textView.text stringByAppendingString:#"\n"];
_textView.text = [_textView.text stringByAppendingString:output];
_textView.text = [_textView.text stringByAppendingString:#"\n>"];
}
This is the textView's content from the simulator :
>hello
Hello World !
>yes
Unknown command
>
Unknown command
>hello
Hello World !
Many ways to do this. If I were doing it I think I would do the following:
A dictionary to hold the input and output for each command
Very large UITextView with all entries in the dictionary outputted in the format you like
A no border UITextField to act as the prompt.
You would have to write the following:
A method to place the UITextFiled at the right line of the UITextField
A method to populate the Diciotnary
A method to populate the UITextField from the dictionary
I have a UITextView. I have the delegate for myTextView set to self and, when I do normal editing, this method calls just fine:
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
NSLog(#"Called");
}
In my app, I call in my code: [myTextView insertText:#"Hello World"];. When I do, I need to call textView:shouldChangeTextInRange:replacementText: after the text is inserted. How do I do this?
Call it explicitly. Call it before editing and only perform the edit if it returns YES. To call it explicitly you need to know the selected range (get the selected range with selectedTextRange), that's it. You already have the text to add and the text view.
Thanks for the answer, #Wain!
Here's what worked:
- (void)insertText
{
NSString *stringToAdd = #"Hello World";
NSString *replacementText = [myTextView.text stringByAppendingString:stringToAdd];
[napkinTextView insertText:stringToAdd];
[self textView:myTextView shouldChangeTextInRange:NSMakeRange(0, stringToAdd.length) replacementText:replacementText];
}