Prevent NSTextField from being left blank - objective-c

I have a NSTextField with an NSNumberFormatter inside of it. I've seen textfields that if you leave them blank it just puts whatever number was in it previously back into it. I'm curious if there's a setting in Interface Builder that provides this behavior. I can't seem to find it, but I'm fairly new to IB and might not be looking in the right spot.
Thanks

There's no behaviour that I know of in IB other than the default value (which won't help here), but you could use NSTextFieldDelegate (extension of NSControlTextEditingDelegate) to monitor when editing finishes, using control:textShouldEndEditing: you can throw a value back into the box if it's left blank. You can read about NSTextFieldDelegate here.

If you want to leave just back some default value for case the user deleted the input
1) Subclass NSNumberFormatter
2) Implement (will put a 0, if empty)
- (NSString *)stringForObjectValue:(id)obj {
if (obj == nil) {
return #"0";
}
return [super stringForObjectValue:obj];
}
3) set the class in IB

Related

How to make NSTextView balance delimiters with a double-click?

It's common to have a text editor for code or other structured content that balances delimiters of some sort; when you double click on a { it selects to the matching }, or similarly for ( ) pairs, [ ] pairs, etc. How can I implement this behavior in NSTextView in Cocoa/Obj-C?
(I will be posting an answer momentarily, since I found nothing on SO about this and spent today implementing a solution. Better answers are welcome.)
ADDENDUM:
This is not the same as this question, which is about NSTextField and is primarily concerned with NSTextField and field editor issues. If that question is solved by substituting a custom NSTextView subclass into the field editor, then that custom subclass could use the solution given here, of course; but there might be many other ways to solve the problem for NSTextField, and substituting a custom NSTextView subclass into the field editor is not obviously the right solution to that problem, and in any case a programmer concerned with delimiter balancing in NSTextView (which is presumably the more common problem) could care less about all of those NSTextField and field editor issues. So that is a different question – although I will add a link from that question to this one, as one possible direction it could go.
This is also not the same as this question, which is really about changing the definition of a "word" in NSTextView when a double-click occurs. As per Apple's documentation, these are different problems with different solutions; for delimiter-balancing (this question) Apple specifically recommends the use of NSTextView's selectionRangeForProposedRange:granularity: method, whereas for changing the definition of a word (that question) Apple specifically states that the selectionRangeForProposedRange:granularity: method should not be used.
In their Cocoa Text Architecture Guide (https://developer.apple.com/library/prerelease/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/TextEditing/TextEditing.html), Apple suggests subclassing NSTextView and overriding selectionRangeForProposedRange:granularity: to achieve this sort of thing; they even say "For example, in a code editor you can provide a delegate that extends a double click on a brace or parenthesis character to its matching delimiter." However, it is not immediately clear how to achieve this, since you want the delimiter match to happen only at after a simple double-click on a delimiter, not after a double-click-drag or even a double-click-hold-release.
The best solution I could come up with involves overriding mouseDown: as well, and doing a little bookkeeping about the state of affairs. Maybe there is a simpler way. I've left out the core part of the code where the delimiter match actually gets calculated; that will depend on what delimiters you're matching, what syntactical complexities (strings, comments) might exist, and so forth. In my code I actually call a tokenizer to get a token stream, and I use that to find the matching delimiter. YMMV. So, here's what I've got:
In your NSTextView subclass interface (or class extension, better yet):
// these are used in selectionRangeForProposedRange:granularity:
// to balance delimiters properly
BOOL inEligibleDoubleClick;
NSTimeInterval doubleDownTime;
In your NSTextView subclass implementation:
- (void)mouseDown:(NSEvent *)theEvent
{
// Start out willing to work with a double-click for delimiter-balancing;
// see selectionRangeForProposedRange:proposedCharRange granularity: below
inEligibleDoubleClick = YES;
[super mouseDown:theEvent];
}
- (NSRange)selectionRangeForProposedRange:(NSRange)proposedCharRange
granularity:(NSSelectionGranularity)granularity
{
if ((granularity == NSSelectByWord) && inEligibleDoubleClick)
{
// The proposed range has to be zero-length to qualify
if (proposedCharRange.length == 0)
{
NSEvent *event = [NSApp currentEvent];
NSEventType eventType = [event type];
NSTimeInterval eventTime = [event timestamp];
if (eventType == NSLeftMouseDown)
{
// This is the mouseDown of the double-click; we do not want
// to modify the selection here, just log the time
doubleDownTime = eventTime;
}
else if (eventType == NSLeftMouseUp)
{
// After the double-click interval since the second mouseDown,
// the mouseUp is no longer eligible
if (eventTime - doubleDownTime <= [NSEvent doubleClickInterval])
{
NSString *scriptString = [[self textStorage] string];
...insert delimiter-finding code here...
...return the matched range, or NSBeep()...
}
else
{
inEligibleDoubleClick = false;
}
}
else
{
inEligibleDoubleClick = false;
}
}
else
{
inEligibleDoubleClick = false;
}
}
return [super selectionRangeForProposedRange:proposedCharRange
granularity:granularity];
}
It's a little fragile, because it relies on NSTextView's tracking working in a particular way and calling out to selectionRangeForProposedRange:granularity: in a particular way, but the assumptions are not large; I imagine it's pretty robust.

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.

Binding single NSCell to multiple values

I've already killed a day on this subject and still got no idea on how could this be done in a correct way.
I'm using NSOutlineView to display filesystem hierarchy. For each row in the first column I need to display checkbox, associated icon and name of the file or directory. Since there's no standard way to make this, I've subclassed NSTextFieldCell using both SourceView and PhotoSearch examples, binding value in IB to name property of my tree item class though NSTreeController. I'm using drawWithFrame:inView: override to paint checkbox and image, forwarding text drawing to super. I'm also using trackMouse:inRect:ofView:untilMouseUp: override to handle checkbox interaction.
Everything was fine up until I noticed that once I press mouse button down inside my custom cell, cell object is being copied with copyWithZone: and this temporary object is then being sent a trackMouse:inRect:ofView:untilMouseUp: message, making it impossible to modify check state of the original cell residing in the view.
Since the question subject is about binding, I thought this might be the answer, but I totally don't get how should I connect all this mess to function as expected. Tried this:
[[[treeView outlineTableColumn] dataCell] bind:#"state"
toObject:treeController
withKeyPath:#"selection.state"
options:nil];
but didn't succeed at all. Seems like I'm not getting it.
May this be a completely wrong way I've taken? Could you suggest a better alternative or any links for further reading?
UPD 1/21/11: I've also tried this:
[[[treeView outlineTableColumn] dataCell] bind:#"state"
toObject:treeController
withKeyPath:#"arrangedObjects.state"
options:nil];
but kept getting errors like "[<_NSControllerTreeProxy 0x...> valueForUndefinedKey:]: this class is not key value coding-compliant for the key state." and similar.
You bind a table (or outline) column's value, not an individual data cell's state. The data cell's object value is set to the current row/col's value then drawn so you don't have potentially thousands (or millions?) of cells created for no good reason.
Further, you want the tree or array controller's arrangedObjects, not its selection.
Bind the column's value to the tree controller's arrangedObjects as the controller key, and "state" as the model key path in IB; or #"arrangedObjects.state" in code as above.
Okay, I've managed to do what I needed by binding columns's value to arrangedObject's self (in IB) and overriding cell's setObjectValue: so that it looks like:
- (void) setObjectValue:(id)value
{
if ([value isMemberOfClass:[MyNodeClass class]])
{
[super setObjectValue:[value name]];
[self setIcon:[value icon]];
[self setState:[value state]];
}
else
{
if (!value)
{
[self setIcon:nil];
[self setState:NSOffState];
}
[super setObjectValue:value];
}
}
Actual state change is performed within another class, connecting its method to cell's selector (in IB) which I call using
[NSApp sendAction:[self action] to:[self target] from:[self controlView]];
from cell's trackMouse:inRect:ofView:untilMouseUp:. This another class'es method looks like this:
- (IBAction) itemChecked:(id)sender
{
MyNodeClass* node = [[sender itemAtRow:[sender clickedRow]] representedObject];
if (node)
{
[node setState:[node state] == NSOnState ? NSOffState : NSOnState];
}
}

Binding returns default value (set with registerDefaults:) instead of zero

Here's my setup:
myTextField is bound to a key in the Shared User Defaults Controller. The user can only enter numbers in the text field.
Each time my application loads, I load default preferences (from the app's Resources folder) using [[NSUserDefaults standardUserDefaults] registerDefaults: ... ].
myMenuItem's title is bound to the same key in the Shared User Defaults Controller.
The issue:
When myTextField is empty, myMenuItem receives the default value that was loaded using registerDefaults: instead of some value that represents the empty field (I would expect 0 or nil).
For example, when the NSTextField is empty the menu item receives "2", the value that was loaded using registerDefaults:, instead of some value that means that the field is empty.
If I comment the registerDefaults: code, the binding returns nil as I would expect when there is nothing in the NSTextField.
I tried to mess around with many of the bindings' settings as well as experiment with placeholder values and I looked at the Cocoa Bindings and User Defaults docs but I could not find the solution.
Expected behavior:
When the text field is empty, I want myMenuItem to reflect that instead of using the default value that was registered using registerDefaults:.
Any help would be greatly appreciated.
I got some hints from the nice folks at irc.freenode.org #macdev and found the following to be the solution to my problem:
Creating a subclass of NSFormatter (or NSNumberFormatter in my case) and overriding getObjectValue:forString:errorDescription: as follows overrides the default behaviour of returning nil (which makes the binding use the registered default value) to instead return 0 when the text field is empty.
- (BOOL)getObjectValue:(id *)anObject forString:(NSString *)string errorDescription:(NSString **)error {
if ([string isEqualToString:#""]) {
*anObject = [NSNumber numberWithInt:0];
return YES;
} else {
return [super getObjectValue:anObject forString:string errorDescription:error];
}
}
A NSValueTransformer subclass' reverse conversion method would have also worked for this.
Note:
The solution above does not work for strings. That is, if your userDefault value is a string and you've bound that to the value of an NSTextField and you subclass NSFormatter and do this:
- (BOOL)getObjectValue:(id *)anObject forString:(NSString *)string errorDescription:(NSString **)error {
if ([string isEqualToString:#""]) {
*anObject = nil;
}
return YES;
}
You'll find that when the NSTextField is empty, the binding is set to whatever value you originally registered using -registerDefaults: rather than to an empty string or a nil value. I attempted to use *anObject = [NSNull null]; as well, but that didn't work either --- the binding is still set to the original, default value.
I ended up setting *anObject = #" "; (a string with a single space) in the code above, so that when the textField is empty, the value in the userDefaults pList is set to the string: #" ". I then check for that string everywhere that uses that userDefault value and respond appropriately (ignore the single space and treat it as if the userDefault value were empty).
It's an ugly hack, but the only way I can find to work around this issue currently. I'm posting it here in case someone else stumbles on this thread. If you ask me, binding an NSTextField to a userDefault should bloody well set that userDefault to an empty string when the textField is empty! Why Apple chose to do it the way they did is beyond me.

String Help - Objective C

I'm trying to make an app that will respond to your command when inserted. So you type in any text in the first box and press enter. It will respond with a response in the 2nd field. I'm not sure how the coding is done here. I'm having trouble with the "if inputbox text = #"whatever", I'm pretty sure that is completely off. Here is the code I have so far (not iphone sdk):
#import "HeliosControl.h"
#implementation HeliosControl
- (IBAction)quitButton:(NSButton *)sender {
}
- (IBAction)sendButton:(NSButton *)sender {
if (inputBox *********) // <------ What do I put in for the asterisks?
{
[outputBox setStringValue:#"Welcome to the SYSTEM"];
}
else
{
[outputBox setStringValue:#"I do not understand your command."];
}
}
#end
BTW I'm a complete noob since I started Objective-C like a week ago.
Second Question:
This is a very simple one, but what would be the coding for closing an application? This is for my quit button.
You want if ([[inputBox stringValue] isEqualToString:#"whatever"]) (assuming inputBox is an NSTextField — otherwise, use the appropriate method for that class to get a string out of it).
Oh, and you can quit the application with [NSApp terminate:self].
Chuck's answer is spot on, but I thought it worth expanding on why you've had problems. There are a number of mistakes in your line:
"if inputbox text = #"whatever"
a) In Objective C you have to use == to check if x is equal to y. So the if statement would be:
if (myFirstVariable == mySecondVariable) { // Do something }
b) A string variable is actually a more complicated thing than a variable just holding a number. That variable's value will actually be the memory address where it is stored. Also, you will usually actually only be using a pointer (denoted by the * when you declare a variable) to the variable.
This means that if you type the following:
if (myFirstVariable == #"Some text")
or
if (myFirstStringVariable == mySecondStringVariable)
Then you're actually only checking for whether they both point to the same bit of memory! Not whether the text is the same. This is why as Chuck explained you need to use the [isEqualToString] method.
Hope that helps!