4
votes

I can't figure out how to get an NSTextfield to update automatically, without having to press "Return" or click another text field.

My goal is to input a number into a field and have the other fields update simultaneously. I tried clicking "Continuous" in the text field attributes but it doesn't seem to do anything.

Here is my interface file:

#import <Foundation/Foundation.h>

@interface InchController : NSObject {
    IBOutlet NSTextField *centimetersTextField;
    IBOutlet NSTextField *inchesTextField;
    IBOutlet NSTextField *feetTextField;
}

-(IBAction)convert:(id)sender;

@end

Here is my implementation file:

#import "InchController.h"

@implementation InchController

- (IBAction)convert:(id)sender {

    if (sender == inchesTextField) {
        float inches = [inchesTextField floatValue];
        [feetTextField setFloatValue:(inches * 0.0833)];
        [centimetersTextField setFloatValue:(inches * 2.54)];
    }
    else if (sender == feetTextField) {
        float feet = [feetTextField floatValue];
        [inchesTextField setFloatValue:(feet * 12)];
        [centimetersTextField setFloatValue:(feet * 30.48)];
    }
    else if (sender == centimetersTextField) {
        float centimeters = [centimetersTextField floatValue];
        [inchesTextField setFloatValue:(centimeters * 0.394)];
        [feetTextField setFloatValue:(centimeters * 0.033)];
    }

}

@end

So here is the updated implementation file per Josh's solution. Commented out the IBAction since it is no longer needed in the implementation and interface files.

#import "LengthController.h"

@implementation LengthController

//- (IBAction) convert: (id)sender {
//}

-(void) controlTextDidChange:(NSNotification *) note {

    NSTextField *changedField = [note object];

    if (changedField == inchesTextField) {
        float inches = [inchesTextField floatValue];
        [feetTextField setFloatValue: (inches * 0.0833)];
        [centimetersTextField setFloatValue: (inches * 2.54)];
    }

    if (changedField == centimetersTextField) {
        float centimeters = [centimetersTextField floatValue];
        [inchesTextField setFloatValue:(centimeters * 0.394)];
        [feetTextField setFloatValue:(centimeters * 0.033)];
    }

    if (changedField == feetTextField) {
        float feet = [feetTextField floatValue];
        [inchesTextField setFloatValue:(feet * 12)];
        [centimetersTextField setFloatValue:(feet * 30.48)];
    }
}

@end
2

2 Answers

7
votes

Make your controller the delegate of the text fields; you can set this in Interface Builder by Ctrl-dragging from the text fields to the controller.

In your controller, implement the "NSControl Delegate" method controlTextDidChange:, which will be called (as its name suggests) whenever the field's text changes. In that method, you can validate the text and, if appropriate, update the contents of the other fields.

The argument that is passed in can give you the text field which changed; you can then pass that on to your existing convert: method to reuse the code:

- (void) controlTextDidChange: (NSNotification *)note {

    NSTextField * changedField = [note object];
    [self convert:changedField];
}

There's nothing special about action methods. The IBAction return type evaluates to void; it's only used by Xcode to expose the method for use in Interface Builder. You can, therefore, call them just like any other method. Here, you get the appropriate field and pass it in as the sender parameter, as if the field had called the action method itself.

0
votes

Depending on the complexity of the problem, bindings may be a viable solution, too.

You can define properties on a model or model controller object and hook them up to the corresponding text fields. Then, changes in the text field are immediately reflected in the properties, which can then trigger changes to other properties.

Text fields bound to these "derived" properties are then updated automatically.

Remember to "bracket" your changes to the derived properties with willChangeValueForKey: and didChangeValueForKey: so the changes are sent to observers. More here.

Of course, if you have loops in the dependencies, it gets ugly; in that case the controlTextDidChange: method mentioned in the other answers is probably better.