4

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
wigging
  • 8,492
  • 12
  • 75
  • 117
  • possible duplicate of [Listen to a value change of my text field](http://stackoverflow.com/questions/6164471/listen-to-a-value-change-of-my-text-field) – jscs Dec 07 '11 at 06:45

2 Answers2

7

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.

jscs
  • 63,694
  • 13
  • 151
  • 195
  • Sweet! It's working. I changed my implementation code to what you mentioned. If you don't mind, please see my answer with the updated code and comment on it. It seems redundant to have the IBAction and delegate use the same **if** loops. – wigging Dec 07 '11 at 15:53
  • @Gavin: Good point, updated. There's nothing special about action methods, so you can just call that from your delegate method. – jscs Dec 07 '11 at 20:15
  • Ok, I did away with the IBAction in the interface and implement files (commented it out). See above for the new implement code. Also, one last question. **How do I format the decimal place in the textfield?** Since I'm using float I get a lot of decimal places. – wigging Dec 07 '11 at 21:00
  • No, no, you still need the `IBAction` part; I just meant that you can call that method the same as you would any other. Formatting is a separate issue. If you [poke around](http://stackoverflow.com/search?q=%5Bobjc%5D+decimal+formatting) I feel confident you'll find answers. If not, feel free to post a new question! – jscs Dec 07 '11 at 21:04
  • Hmmm, well I deleted the IBAction and all related connections out of the program and the app works just fine. – wigging Dec 07 '11 at 21:11
0

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.

Monolo
  • 18,205
  • 17
  • 69
  • 103