How to capture user input in real time in NSTextField? - objective-c

I can capture string when user click the button.
and I also use the following method
- (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)fieldEditor
which declared in NSControlTextEditingDelegate Protocol.
And when user begin editing, the button will be available.
My Question is:
How to make the button disable when user delete all text (make the textField empty without clicking button)?
The above method seems can not do it...

If your NSText is a NSTextView you may also use NSTextDelegate Protocol and NSTextViewDelegate Protocol.
Among others, the NSTextDelegate Protocol declares this delegate method:
textDidChange:
Informs the delegate that the text object has changed its characters or formatting attributes.
- (void)textDidChange:(NSNotification *)aNotification
Discussion
The name of aNotification is NSTextDidChangeNotification.
The NSTextDidChangeNotification is documented here NSTextDidChangeNotification

Look at using the NSControlTextDidChangeNotification or controlTextDidChange: delegate method (from NSControl) which is posted by the text field. When you receive the callback you can examine the text currently in the field to decide what to do.

Many ways to do the same thing. This version lets you easily catch specific keystrokes of interest.
- (BOOL)textView:(NSTextView *)aTextView shouldChangeTextInRange:(NSRange)affectedCharRange replacementString:(NSString *)replacementString{
NSLog(#"last character entered: %#",replacementString);
}
for instance, if you were looking for the return key, you might then implement:
if ([replacementString characterAtIndex:0] == NSNewlineCharacter)
NSLog(#"return pressed");

Related

Obj-C IBAction (id)sender

I have an IBAction like:
- (IBAction)thisThing:(id)sender {
[self doSomething];
}
I would like do this (manually call the IBAction):
[self thisThing];
However I obviously need to do [self thisThing:...];. <- (what the heck goes after the colon?)
I'm not sure what (id)sender is supposed to be. How do call it manually without needing to click the button that it's tied to? I searched for anything about IBAction (id)sender and the results came up completely empty.
what the heck goes after the colon?
Well it depends on how you have written code inside the IBAction. Say for a calculator app, if all buttons are hooked up with the same IBAction then you would need sender (in this case NSButton) to identify which button got touched/clicked.
-(IBAction) buttonClicked:(id) sender {
// sender's identifier or Tag will let us know the number clicked here
[self doSomeThing];
}
But if you had IBActions for each and every button you would not need to be dependent on sender.
-(IBAction) firstButtonClicked:(id)sender;
-(IBAction) secondButtonClicked:(id)sender;
and so on ...
So in the first case if I want to programatically invoke the action I would pass the sender with appropriate attributes set to make sure the correct button got clicked. In second case just pass nil as it does not depend upon sender's value.
While popeye's comment answers your question, here's another perspective.
You have complete control over the action method. If you aren't using the sender parameter for anything in that method, then you do not have to supply it when calling it manually. By not supply it I mean pass nil as the value of the parameter.
Normally, it will contain a pointer to the control that is wired up to the action. If you did want to use if for something, they you would simply cast sender to the type of the control and do whatever with it.
- (IBAction)thisThing:(id)sender
In here,
- denotes the start of a instance method, whereas + means class(static) method.
( .. ) indicate return type. IBAction is actually void. Using IBAction instead of void tells that this method will be associated with UI(.nib) events.
thisThing is the name of the method, followed by parameter list.
In C view point, actual function names is something like thisThing:. That is, number of parameter modifies function name (external linkage).
If you meant to call thisThing: but write [self thisThing], you are calling different (not existing) method.
So, you must write :. What actual value to pass? One can decide this by looking at the method implementation.
If you have the IBOutlet of the button, you can pass that like [self thisThing:btn];
Or simply pass nil, [self thisThing:nil]; (if you are not using sender inside the IBAction)
- (IBAction)thisThing:(id)sender {
}
Is an event handler. That means that it is called when an event is sent by someone. A typical example of an event is a click on a button, it that case, the button sends the event (that means the button, a NSButton instance, is the sender).
Having the sender as a parameter is useful when you use the same event handler for events coming from different sources, e.g.
- (IBAction)buttonTapped:(id)sender {
if (sender == self.myButton1) {
//button 1 was tapped
}
else if (sender == self.myButton2) {
//button 2 was tapped
}
}
If this case, if you want to call the event handler manually, you just call
[self buttonTapped:self.myButton1];
If you don't use the sender parameter, then you can simply call
[self buttonTapped:nil];
However, the parameter is completely optional, so you can eliminate it:
- (IBAction)buttonTapped {
// ...
}
[self buttonTapped];
On a separate note, it's never good to call event handlers manually. Event handlers serve to handle events. If you need to perform the same action manually, separate it from the event handler, e.g.
- (IBAction)buttonTapped {
[self doSomething];
}
instead of calling [self buttonTapped], call [self doSomething]

Is it possible to subclass NSSavePanel?

I am wondering if there is a way to subclass NSSavePanel. Or if you were to create a dummy object, how would you mimic the beginSheetModalForWindow:CompletionHandler function of NSSavePanel?
-(void)beginSheetModalForWindow:(NSWindow *)window completionHandler:(void (^)(NSInteger *))handler{
I am blanking out on how to implement the block handler when implementing the function in the .m class file.
Short Answer: No
Longer Answer: Here Be Dragons
It is possible but stuff will probably break. You can also add methods using a category and they might work, or they might not. The problems arise due to the way NSOpenPanel is implemented to support the App Sandbox - various chicanery is going on behind the scenes and even convenience category methods which just call existing methods on the class can result in errors being reported by OS X and dialogs not to appear. NSOpenPanel is a delicate creature that should be touched as little as possible and only ever with great care.
Wrapping an NSOpenPanel instance in another class is a different story and should not upset it at all. Go that route.
Addendum re: Comment
The declaration of beginSheetModalForWindow is:
- (void)beginSheetModalForWindow:(NSWindow *)window completionHandler:(void (^)(NSInteger result))handler
The completion handler gets passed a value indicating which button was pressed. To take action dependant on that you can use a standard if:
NSOpenPanel *openPanel;
NSWindow *hostWindow;
...
[openPanel beginSheetModalForWindow:hostWindow
completionHandler:^(NSInteger returnCode)
{
if (returnCode == NSFileHandlingPanelOKButton)
{
// OK pressed
...
}
else
{
// Cancel pressed
...
}
}
];

Update property bound from text field without needing to press Enter

I have a text field and I bind it to an NSString instance variable.
When I type in the text field, it does not update the variable. It waits until I press the Enter key. I don't want to hit Enter every time.
What do I need to change in order to make the binding change value immediately?
By default, the value binding of an NSTextField does not update continuously. To fix this, you need, after selecting your text field, to check the "Continuously Updates Value" box in the Bindings Inspector under the Value heading:
However, most often, what you really want to do is update the property to which the text field is bound when the user has finished editing and presses a button ("Save" or "OK", for example). To do this, you needn't continuously update the property as described above, you just need to end editing. Daniel Jalkut provides an extremely useful implementation of just such a method:
#interface NSWindow (Editing)
- (void)endEditing;
#end
#implementation NSWindow (Editing)
- (void)endEditing
{
// Save the current first responder, respecting the fact
// that it might conceptually be the delegate of the
// field editor that is "first responder."
id oldFirstResponder = [oMainDocumentWindow firstResponder];
if ((oldFirstResponder != nil) &&
[oldFirstResponder isKindOfClass:[NSTextView class]] &&
[(NSTextView*)oldFirstResponder isFieldEditor])
{
// A field editor's delegate is the view we're editing
oldFirstResponder = [oldFirstResponder delegate];
if ([oldFirstResponder isKindOfClass:[NSResponder class]] == NO)
{
// Eh ... we'd better back off if
// this thing isn't a responder at all
oldFirstResponder = nil;
}
}
// Gracefully end all editing in our window (from Erik Buck).
// This will cause the user's changes to be committed.
if([oMainDocumentWindow makeFirstResponder:oMainDocumentWindow])
{
// All editing is now ended and delegate messages sent etc.
}
else
{
// For some reason the text object being edited will
// not resign first responder status so force an
/// end to editing anyway
[oMainDocumentWindow endEditingFor:nil];
}
// If we had a first responder before, restore it
if (oldFirstResponder != nil)
{
[oMainDocumentWindow makeFirstResponder:oldFirstResponder];
}
}
#end
So if for example you had a "Save" button targeting your view controller's method -save:, you would call
- (IBAction)save:(id)sender
{
[[[self view] window] endEditing];
//at this point, all properties bound to text fields have the same
//value as the contents of the text fields.
//save stuff...
}
The previous answer is beautiful, and I learned from it about tricking the Window/View/Document system to end-editing on everything at the programmer's will.
However, the default responder chain behavior (including the preservation of the first responder until the USER moved their focus to something else) is fundamental to the Mac's "look and feel" and I wouldn't mess with it lightly (I swear I did very powerful things in responder-chain manipulation, so I don't say that out of fear.)
In addition - there is even a simpler method - that does not require changing the binding. In the Interface-builder, select the text field, and select the "Attribute Inspector" tab. You'll see the following:
Checking the red-circled "continuous" will do the trick. This option is basic and older even than binding, and its main use is to allow validator object (a whole new story) to validate the text and change it on the fly, as the user types. When the text-field calls validator calls, it also updates bound values.

How do I handle a button tap according to Clean Code principles?

I have the following, seemingly simple piece of code handling button taps in an iOS application:
- (IBAction)tapKeypadButton:(UIButton *)sender {
NSString *buttonLabel = sender.titleLabel.text;
if ([buttonLabel isEqualToString:#"<"]) {
[self _tapBackButton];
} else {
[self _tapDigitButton:buttonLabel];
}
}
To completely follow the Clean Code principles by Robert C. Martin, would I need a ButtonTapFactory or something in the same line?
You have two types of buttons, with different behaviors (back button and digit button). To make this code clean, you should have two actions for each type. The type should not be determined by the contents of the text inside the button, but through a semantically meaningful way. (i.e. subclass).
Further, an action method should only contain a call to another method that does the actual logic. Everything else is not testable. In code:
- (IBAction) tapBackButton:(id) sender
{
[self _tapBackButton:sender];
}
- (IBAction) tapDigitButton:(id) sender
{
[self _tapDigitButton:sender];
}
This way you can have unit tests calling your methods without your UI code interfering. Please also note that I removed the label from the call to _tapDigitButton. The digit should not be parsed from the label, but be passed in a more semantically stable way, for example using the tag property.

NSTextField autocompletion delegate method not called

I implemented the following delegate method for NSTextField to add autocompletion support:
- (NSArray *)control:(NSControl *)control
textView:(NSTextView *)textView
completions:(NSArray *)words
forPartialWordRange:(NSRange)charRange
indexOfSelectedItem:(NSInteger *)index
The issue is that this method never gets called. I can verify that the delegate of the NSTextField is set properly because the other delegate methods function as they should.
You'll need to get complete: called on the text field's field editor at some point. That's what triggers the completions menu, but it doesn't get called automatically. If you don't have F5 bound to anything, try typing in your field and hit that. Completion should trigger then; Option-Esc may also work.
If you want auto completion, it takes some work. You could start with something like this:
- (void)controlTextDidChange:(NSNotification *)note {
if( amDoingAutoComplete ){
return;
} else {
amDoingAutoComplete = YES;
[[[note userInfo] objectForKey:#"NSFieldEditor"] complete:nil];
}
}
Some kind of flag is necessary because triggering completion will make NSControlTextDidChangeNotification be posted again, which causes this to be called, triggering completion, which changes the control text, which...
Obviously, you'll need to unset the flag at some point. This will depend on how you want to handle the user's interaction with autocompletion -- is there likely to only be one completion for a given start string, or will the user need to keep typing to narrow down possibilities (in which case you'll need to trigger autocompletion again)?
A simple flag might not quite do it, either; it seems that although the notification is re-posted, the field editor's string won't have changed -- it will only change in response to direct keyboard input. In my implementation of autocomplete, I found that I had to keep a copy of the "last typed string" and compare that each time to the field editor's contents.