Binding NSSlider to NSTextField with nil minValue? - objective-c

I have an NSSlider bound to a text field to display the value given by the slider.
Everything works, but I would like the NSSlider to send "nil" as minValue to the object.
For example, I can convert it to nil when saving (to a propertylist):
if ([myOb intValue] > 0)
[myDict setObject:... forKey:...]
But I would like to apply same behavior when the app is running, cause some other fields are enabled (with binding) only if the value of "myObj" is nil. The problem is the NSSlider always returns "0", and minValue=nil is not accepted by NSSlider.
Thanks in advance,
Ronan.

It won't accept nil as the minValue. The reason, if you look at the NSSlider documentation, is because minValue and maxValue are both defined as doubles, which are not object types.
If you want to customize the value that is displayed in the text field, you would probably want to create an NSValueTransformer subclass to convert double values to NSString values for the text field. Read the Value Transformer Programming Guide for details on how to do this. I have done this in the past so that I can have a label that displays time values similar to the screen saver slider in System Preferences. This works quite well.

Finally i solved my problem, by associate the slider to an IBAction:
- (IBAction)slideValue:(id)sender {
if ([mySlide intValue]==0) { //slider
[myNumBox setStringValue:#""]; //textfield
[myValue setValue:nil]; // value
}
}
So, when the slider is equal to O, it return "" in the textfield (nil don't works).

Related

Cocoa - controlTextDidEndEditing issue

I have the following delegate method which is listening for when text changes in an editable field:
-(void)controlTextDidEndEditing:(NSNotification *)aNotification{
Say, I have two NSTextFields declared, how can I find out which is the NSTextField that generated the notification? I want to perform some code when one text field's text did finish editing and other code when the other text field's code finishes editing.
In other words, how can I get the name of the text field that gave the notification?
Thank you!
[aNotification object] is the NSControl (or NSControl subclass) object which posted the notification.
You could use tags and get the tag field of the object, but first you have to force downcast the object from Any to NSTextField so you can access the tag property (an object of "any" doesn't have a "tag" property) like so:
let object = aNotification.object as! NSTextField
then you can check the tag:
if object.tag == 99 { do something }
That's how I addressed the issue in some of the code I'm working on now.

Prevent NSTextField from being left blank

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

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];
}
}

How to click a checkbox in nstableview and update an object using KVC?

I am trying to learn cocoa and have a few problems with KVC and bindings. I have a nstableview with three columns; "checkbox", "text", "icon". The values of each column is binded to an arraycontroller using KVC. When program is launched the rows and columns are correctly filled into the tableview according to the values in the array. I can click a row and correctly print the content of that row using something like this:
- (IBAction)fileTableViewSelected:(id)sender{
NSInteger r;
NSDate *fModOne;
id object;
r = [[NSNumber numberWithInt:[sender selectedRow]] intValue];
object = [arrayIntersect objectAtIndex:r];
fModOne = [object valueForKey:#"fileModifiedDirOne"];
NSLog(#"Date found in row is %#",fModOne);
}
My problem is when I try to click the checkbox in column one and change the value of the box. Initially, the value of the checkbox is set to 1 using the arraycontroller which works fine, but when I want to change the value of the checkbox of a specific row to 0 by clicking on it the program crashes. When the box is clicked an action is correctly called and this is where I thought I could simply change the value of my objects BOOL by calling:
[object setValue:[NSNumber numberWithBool:NO] forKey:#"doSync"];
My setters and getters for the BOOL doSync is defined as:
#property(nonatomic, readwrite) BOOL doSync;
#dynamic doSync;
- (void)setDoSync:(BOOL) value{
NSLog(#"setting dosync %i", value);
doSync = NO;
}
- (BOOL)doSync{
return doSync;
}
I have searched everywhere for a solution to my problem, but I am unable to find any examples of how to use checkboxes in tableview using KVC and bindings. I appreciate any help I can get on this and I would appreciate any examples I could take a look at.
Cheers and thanks! Trond
You don't need to implement this yourself as an action. Just bind the column through your array controller's arrangedObjects to the doSync property of the model objects.
If you don't want to use Bindings, you still shouldn't implement it as an action. Instead, be the table view's data source and respond to the message the table view will send you to change one of the values.
#dynamic doSync;
There's no reason to have this if you turn around and implement the accessors for that property in the same class.
If this is a managed-object class and the property is an attribute of the entity, then your accessors should send [self willAccessValueforKey:] before and [self didAccessValueForKey:] after accessing the instance variable. If that's all they do, then you should not implement the custom accessors at all; cut them out and have #dynamic alone.
- (void)setDoSync:(BOOL) value{
doSync = NO;
That's not setting the property to the value passed in.

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.