Want to make UITextView react to return key - objective-c

I'm trying to implement a program in Xcode that's somewhat like a command line. I basically have a UITextView that can take multiple lines of text. Right now, I have a button that will make the necessary changes after the user is done entering commands, but I want to be able to have a method that gets called after the user hits the return key in the UITextView, so basically it makes changes after each command. Is it possible to do this?

The BOOL method mentioned above is a wrong answer... for one the person is checking the text from the TextView the moment before it is updated so they are viewing the old text... Also the methods are out of date.
This usage will work immediately once the return key is pressed (The current "answer" will not work until after the return key has been pressed and then ANOTHER key is pressed):
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if ([text isEqualToString:#"\n"]) {
NSLog(#"Return pressed");
} else {
NSLog(#"Other pressed");
}
return YES;
}
Don't forget to add the UITextViewDelegate to your .h file's protocols.
#interface ViewController : UIViewController <UITextViewDelegate> {
and set yourTextView.delegate = self; in .m file!
/*
note: This will also get called if a user copy-pastes just a line-break...
unlikely but possible. If you need to ignore pasted line-breaks for some
reason see here: http://stackoverflow.com/a/15933860/2057171 ...
Now for an unrelated-tip: If you want to accept pasted line breaks however
I suggest you add an "or" to the conditional statement and make it also
check if "text" isEqualToString #"\r" which is another form of line-break
not found on the iOS keyboard but it can, however, be found on a website
and copy-pasted into your textView. If you want to accept pasted text
with a line-break at the end you will need to change the
"isEqualToString" code above to say "hasSuffix", this will check for
any string %# with a "\n" at the end. (and again for "\r") but make
sure you don't call your "next" method until after `return YES;`
has been called and the text view has been updated, otherwise
you will get only the text that was there before the copy paste
since this is "shouldChangeTextInRange" method, not
"didChangeTextInRange", if you do this I suggest stripping the
"\n" or "\r" from the end of your final string after the copy-paste
was made and applied and the text was updated.
*/

If you set a delegate to the UITextView which implements
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
Then you can check if the last character is "\n", and get the text entered since the last command by doing
NSArray* components = [textView.text componentsSeparatedByString:#"\n"];
if ([components count] > 0) {
NSString* commandText = [components lastObject];
// and optionally clear the text view and hide the keyboard...
textView.text = #"";
[textView resignFirstResponder];
}

Note, I haven't tested this, just an idea:
- (void)textViewDidChange:(UITextView *)textView
{
if ([[textView.text substringWithRange:NSMakeRange(textView.text.length - 1, 1)] isEqualToString:#"\n"])
{
[textView resignFirstResponder];
[self methodYouWantToCall];
}
}

You can do this by setting up a delegate for the UITextView. See UITextViewDelegate Protocol Reference for details on what can be done.

Related

Special auto-completion behaviour for NSComboBox

I have an NSComboBox containing several items. If the user types while the list is open, I would like to select the appropriate item within the list (if there is a corresponding item).
The twist: The string in the text field is not necessarily equal to the corresponding item in the list. (The strings within the list may e.g. contain additional explanations.)
I tried implementing textDidChange:
- (void)textDidChange:(NSNotification *)notification {
NSString* nsstring = [self stringValue];
[self selectItemAtIndex: [self findCorrespondingIndex: nsstring]]; // changes text - even if documentation states that it "does not alter the contents of the combo box’s text field"
}
But, contrary to the documentation, selectItemAtIndex changes the text in the text field, so I tried the following:
- (void)textDidChange:(NSNotification *)notification {
NSString* nsstring = [self stringValue];
[self selectItemAtIndex: [self findCorrespondingIndex: nsstring]]; // changes text - even if documentation states that it "does not alter the contents of the combo box’s text field"
[self setStringValue:nsstring]; // clears selection
}
But, contrary to the documentation, setStringValue clears the selection.
Is this behavior expected?

shouldChangeCharactersInRange doesn't get the last number

I have this code that should catch exactly what the user types:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSLog(#"Catch -> %#",textfield.text);
}
But When the user digit the letter A, the console return me ('null'), When the user digit the letter AB, the console return me only the letter A, in this case the method shouldChangeCharactersInRange doesn't return the last letter that user types.
To change this I try to change my code to:
NSLog(#"Catch -> %#",string);
In this case, the console shows me only the last number. How can I solve this problem and pick the complete sentence that user digit?
This method is called BEFORE the text field has changed. If you speak English, you would have already known this just by reading the method name, one of the big advantages of Cocoa...
This method gives you a chance to say "NO, don't change the text in this text box!" or to change the text in the box before the change is made, or to change the text that will be added before it's added.
textfield.text won't reflect the change until after this method returns.
To catch the END of editing events for a textfield, put this in your viewDidLoad:
[yourTextField addTarget:self
action:#selector(textFieldDidChange:)
forControlEvents:UIControlEventEditingChanged];
Where yourTextField is a reference to the text field of interest.
Now write the method:
- (void)textFieldDidChange:(UITextfield *)textfield {
NSLog(#"Catch -> %#", textfield.text);
}
This delegate method for me usually starts out like this:
- (BOOL)textField:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
NSString *candidateString = [textView.text stringByReplacingCharactersInRange:range withString:text];
Now, candidateString will contain what you expect. It's done this way so that any manner of input (even a paste into the middle of the text) can be understood before and after the text field's text is updated

How can I insert text into UITextField at the current cursor position?

I'm trying to use the UITextField's "return" key to insert a custom character. Here's what my UITextFieldDelegate method looks like:
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField insertText:#"¶"];
return NO;
}
Unfortunately, this only works some of the time:
"one two|" --> move cursor --> "one| two" --> return --> "one¶| two" (OK)
"onetwo|" --> return --> "onetwo¶|" (OK)
"onetwo|" --> move cursor --> "one|two" --> return --> "onetwo¶|" (FAIL)
In the last case I would have expected "one¶|two".
How do I ensure that the inserted text is always inserted at the cursor position?
Thanks.
The problem is when you tap the return key on the keyboard, the text field sets the selected range (the cursor position) to the end of its text before it sends you the textFieldShouldReturn: message.
You need to keep track of the cursor position so you can restore it to its prior position. Let's say you have a reference to the text field in a property:
#interface ViewController () <UITextFieldDelegate>
#property (strong, nonatomic) IBOutlet UITextField *textField;
#end
You'll need an instance variable to hold the prior selected text range (from before the return key was tapped):
#implementation ViewController {
UITextRange *priorSelectedTextRange_;
}
Then you can write a method that saves the selected text range to the instance variable:
- (void)saveTextFieldSelectedTextRange {
priorSelectedTextRange_ = self.textField.selectedTextRange;
}
and in textFieldShouldReturn:, before you insert the pilcrow, you can change the selected text range back to its prior value:
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
textField.selectedTextRange = priorSelectedTextRange_;
[textField insertText:#"¶"];
return NO;
}
But how can we make the system send the saveTextFieldSelectedTextRange message when we need it to?
The UITextFieldDelegate protocol doesn't have messages for changes to the selected range.
UITextField doesn't post any notifications for changes to the selected range.
The UITextInputDelegate protocol does have selectionWillChange: and selectionDidChange: messages, but the system sets the text field's inputDelegate to its own UIKeyboardImpl object when the text field begins editing, so we can't use the inputDelegate.
Key-value observing on the text field's selectedTextRange property isn't reliable. In my testing on the iOS 6.0 simulator, I don't get a KVO message when I move the cursor from the middle to the end of the text by tapping the text field.
The only way I can think of to reliably track changes to the text field's selected range is by adding an observer to the run loop. On every pass through the event loop, the observer runs before event processing, so it can grab the current selected range before it changes.
So we actually need another instance variable, to hold the reference to our run loop observer:
#implementation ViewController {
UITextRange *priorSelectedTextRange_;
CFRunLoopObserverRef runLoopObserver_;
}
We create the observer in viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
[self createRunLoopObserver];
}
and we destroy it in both viewDidUnload and in dealloc:
- (void)viewDidUnload {
[super viewDidUnload];
[self destroyRunLoopObserver];
}
- (void)dealloc {
[self destroyRunLoopObserver];
}
To create the observer, we need a plain old C function for it to call. Here's that function:
static void runLoopObserverCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
__unsafe_unretained ViewController *self = (__bridge ViewController *)info;
[self saveTextFieldSelectedTextRange];
}
Now we can actually create the observer and register it with the main run loop:
- (void)createRunLoopObserver {
runLoopObserver_ = CFRunLoopObserverCreate(NULL, kCFRunLoopAfterWaiting, YES, 0, &runLoopObserverCallback, &(CFRunLoopObserverContext){
.version = 0,
.info = (__bridge void *)self,
.retain = CFRetain,
.release = CFRelease,
.copyDescription = CFCopyDescription
});
CFRunLoopAddObserver(CFRunLoopGetMain(), runLoopObserver_, kCFRunLoopCommonModes);
}
and here's how we actually deregister the observer and destroy it:
- (void)destroyRunLoopObserver {
if (runLoopObserver_) {
CFRunLoopRemoveObserver(CFRunLoopGetMain(), runLoopObserver_, kCFRunLoopCommonModes);
CFRelease(runLoopObserver_);
runLoopObserver_ = NULL;
}
}
This approach works in my testing on the iOS 6.0 simulator.
What's happening here is that you're not keeping track of the insertion point, also known as the selection range.
And to do that, you need to get somewhat deeper into the guts of what UITextField can do.
Using UITextInput (accessible as a protocol that UITextField uses), you can fetch the "selectedTextRange" property which tells you where the caret (cursor, insertion point) is and that's where you should insert your special character. This should work if you set your object to be a delegate that conforms to the "UITextInput" protocol.

CoreData How to filter NSFetchedResultsController with Search and ARC

I found this perfect answer to search through an NSFetchedResultsController : https://stackoverflow.com/a/4481896/1486928
EDIT : project showing the issue : http://cl.ly/2x0C0N0E4240
It seems really great except it wasn't written to use with ARC, I tried to just remove all the "retain" "release" "autorelease".
It still works, well mostly, the thing is, when I enter a char in the searchbar it shows the filtered table as expected but it only takes 1 char (if you add more it doesn't do anything) and after that every other "search" will show the results of the first search that only took 1 char.
I've been at it for 2 days putting NSlog anywhere to see when every methods are called but still couldn't find how to make it work :(
Thanks !
Edit : here is .m http://pastebin.com/9U4TfbA6
Edit : here is .h http://pastebin.com/S9aaNRFE
Also if it can help the search works when I comment this :
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController_! = nil)
{
return fetchedResultsController_;
}
...
}
And this :
- (NSFetchedResultsController *)searchFetchedResultsController {
if (searchFetchedResultsController_ != nil)
{
return searchFetchedResultsController_;
}
...
}
But it mess up other things :/
I guess that you are messing up with the search display controller delegate methods,
and especially you need to check this method
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString;
Because this method will reload your table view for every character which you type in your search field as the name itself suggests shouldReloadTableForSearchString
Edit:
Well you need to implement 2 delegate methods of UISearchBar because all your UISearchDisplayController delegate methods are same and those 2 methods are
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText;
This tells the delegate that the user changed the search text.
- (BOOL)searchBar:(UISearchBar *)searchBar shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
This is used if the text in a specified range should be replaced with given text.
and please note these methods are called several times, i mean for the every character added and deleted in the search bar and because you are setting the searchFetchedResultsController to nil every time the search text changes
just comment out this part, it will work well
/*if (searchFetchedResultsController_ != nil)
{
NSLog(#"Returned !nil searchController");
return searchFetchedResultsController_;
}*/

Drag and drop between nstableviews in nscollectionview sets window controller property to nil?

In the main window of my application, I have a collection view such that each collection view item contains an NSTableView. The window controller for this window has an NSString * property projecttitle. This property is bound to an NSTextField in the window for which I have overridden the default return key behavior so that the user can hit Return and write a carriage return into the text field. Now, after changing the string in the text field and THEN dragging an item between the table views for two different collection view items, the projecttitle property becomes nil. I sort of feel like maybe I just have way too much going on here and that this bug will be impossible to track down, but maybe someone has seen something even remotely similar to this behavior elsewhere?
EDIT: Putting a breakpoint on the projecttitle property doesn't seem to yield anything useful. The program execution does not break at all upon dragging and dropping, but the property will indeed be nil after this.
EDIT 2: After more digging around, it appears the behavior is related to the NSFormatter object. It happens not only when dragging and dropping, but apparently any time the nstextfield attempts to resign as responder. This behavior stops when I disconnect the formatter object in IB. Here's the string validation code for the formatter which forces the string to be less than or equal to 4 lines long and with each line being no longer than 32 characters. This seems to work fine when actually typing the in the text field, but apparently, not after.
-(BOOL)isPartialStringValid:(NSString **)partialStringPtr
proposedSelectedRange:(NSRangePointer)proposedSelRangePtr
originalString:(NSString *)origString
originalSelectedRange:(NSRange)origSelRange
errorDescription:(NSString **)error {
NSArray * lines = [*partialStringPtr componentsSeparatedByString:#"\n"];
if ( [lines count] > 4 ) {
return NO;
}
for (NSString * line in lines) {
if ( [line length] > self.maxlength ) {
return NO;
}
}
return YES;
}
Okay, solved. Turned out it was the -getObjectValue:forString:errorDescription: method. I had it implemented as
-(BOOL)getObjectValue:(id *)obj forString:(NSString *)string errorDescription:(NSString **)error {
*obj = string;
return YES;
}
Changing *obj = string to *obj = [NSString stringWithString:string] fixed everything right up.