Here is a simple tutorial by a list member of ours:
http://juliuspaintings.co.uk/cgi-bin/paint_css/animatedPaint/059-NSStepper-NSTextField.pl
This example uses an object controller but can be reconfigured to bypass it and
bind to an ivar easily.
Even though it uses a number formatter besides the controller it exposes the
problem you are facing, too:
(1) Validation happens only when you leave the field.
(2) The user is not prevented from entering crap values right on the spot.
I took it as a base for some experiments of my own:
By setting validates immediately in IB for the text field and putting a handler
like
- (BOOL)validateFloatValue:(id *)ioValue error:(NSError **)outError
{
NSLog(@"validating");
return YES;
}
into the model class, you can prove that whenever you type a number digit into
the text field you see a validating message in the console, but at the same
time the user may happily type any other crap without the validation even
bothering to kick in. Validation furthermore stops if the value exceeds the max
value defined in the number formatter. I could not find anything about this in
the KVC docs, so go figure. I hope it's just me doing something terribly wrong
or Apple's implementation is rather usless for this purpose.
I guess you might have to implement this method as a supllement if your value
is set programmatically from various other sources.
All in all it seems way to complicated to achieve the desired effect without
use of the delegate method. I'd be happy to see an example which does not have
to rely on it, if this is possible at all. Maybe you have to use a custom value
converter in the bindings or something. Dunno.
Therefore, my own implementation of a text field + stepper combination goes
like this:
Specs:
- Only integers are allowed. Single 0 inputs, which eventually would lead to
leading zeros, are forbidden, since the...
- minimum value is hardcoded at 2 anyway.
- The maximum value is calculated in code (by the window controller's delegate).
So I have a window controller sub-subclass for a modal window implemented like
this (only relevant parts are shown here):
MyWC.h
@interface MyWC : ContextMenuWC {
IBOutlet NSTextField *unitNumberField;
IBOutlet NSStepper *unitNumberStepper;
NSInteger unitSize;
IBOutlet id delegate;
}
@property (nonatomic, assign) NSInteger unitSize;
@property (nonatomic, assign) id delegate;
- (IBAction) okAction:(id)sender;
- (id)initWithDelegate:(id <SplitColumnToRowsDelegate>)theDelegate;
@end
The stepper's value is bound directly to unitSize. IB binding setting is
validates immediately.
The NSTextField is equally bound to unitSize. IB binding settings are all off.
MyWC.m
#import "RegexKitLite.h"
- (void)controlTextDidChange:(NSNotification *)aNotification
{
NSText *theFieldEditor = [aNotification.userInfo
objectForKey:@"NSFieldEditor"];
NSString *theString = [theFieldEditor string];
//0-9 only, but no single 0 (i.e. 0 only allowed in double or more digit
numbers)
BOOL isAcceptableIntegersOnly = ([theString
isMatchedByRegex:@"^[0-9]{1,2}$"] && ![theString isMatchedByRegex:@"^0{1,2}$"]);
if (!isAcceptableIntegersOnly) {
//fall back to max value if input is bad
[theFieldEditor setString:[NSString stringWithFormat:@"%ld",
(NSInteger)[unitNumberStepper maxValue]]];
[theFieldEditor setSelectedRange:NSMakeRange(0, [[theFieldEditor string]
length])];
self.unitSize = [[theFieldEditor string] integerValue];
[self.okButton setEnabled:YES];
return;
}
//regex matches, so the following is safe, otherwise integerValue just
extracts digits from any kind of string value
NSInteger theInteger = [theString integerValue];
NSInteger theMinValue = (NSInteger)[unitNumberStepper minValue];
//if 10 shall be allowed as input, the user has to be able to type 1
if (theInteger < theMinValue) {
[self.okButton setEnabled:NO];
return;
}
if (theInteger > (NSInteger)[unitNumberStepper maxValue]) {
//fall back to max value if input is too high
[theFieldEditor setString:[NSString stringWithFormat:@"%ld",
(NSInteger)[unitNumberStepper maxValue]]];
[theFieldEditor setSelectedRange:NSMakeRange(0, [[theFieldEditor string]
length])];
}
[self.okButton setEnabled:YES];
self.unitSize = [[theFieldEditor string] integerValue];
}
Note that I initialize the bound variable to some predefined value (the maximum
value possible in this case).
Moreover, even though both GUI elements are bound to self.unitSize, I set this
value "manually" at the end of the delegate method to make things work.
Took me quite a bit of experimentation to get it as I wanted it. I also
experimented with KVC validation methods for unitSize but these also didn't
help.
(Furthermore, I experimented with a @property (copy) previousUnitSizeString as
a backup to restore a previously accepted value if the code rejects the user
input, but the user experience didn't feel right for the purpose, so I always
fall back to the max value. Maybe I'll change my mind eventually to simply
reject bad input.)
Hope this helps.
Am 17.12.2011 um 05:04 schrieb Koen van der Drift:
>
> On Dec 16, 2011, at 10:40 AM, Mike Abdullah wrote:
>
>> Your text field is is bound to the model/a controller right? If so, you want
>> the "updates immediately" binding option.
>>
>> On 16 Dec 2011, at 12:59, Koen van der Drift wrote:
>>
>>> On Thu, Dec 15, 2011 at 10:50 AM, Koen van der Drift
>>> <[email protected]> wrote:
>>>> On Thu, Dec 15, 2011 at 10:17 AM, Mike Abdullah
>>>> <[email protected]> wrote:
>>>>
>>>>> NSStepper is a subclass of NSControl. Hook up its action/target to be
>>>>> notified when it's adjusted.
>>>>
>>>> I'll try that, thanks. Using bindings sometimes makes you forget that
>>>> there is still some cdong needed :)
>>>>
>>>> - Koen.
>>>
>>> (with cdong, I meant coding :)
>>>
>>> Adding an IBAction did the trick indeed. One aditional question, how
>>> do I make the textfield immediately send the updated value to
>>> controlTextDidChange without the need of htting enter of tabbing out
>>> of the field? See eg the Date/Time preference panel.
>>>
>>> - Koen.
>>
>
>
> It's not working yet. Whenever I type in the NSTextField, the number shows up
> twice, eg if I type '6', I see '66'. And I get the message below in the
> debugger console.
>
> Is there some (Apple) sample code that shows how to use a
> NSTextField/NSStepper combination bound to an integer value?
>
> Thanks,
>
> - Koen.
>
>
>
> 2011-12-16 22:45:22.286 MyApp[4566:503] -[__NSCFConstantString
> unsignedLongLongValue]: unrecognized selector sent to instance 0x7fff7847da00
> 2011-12-16 22:45:22.287 MyApp[4566:503] Exception detected while handling key
> input.
> 2011-12-16 22:45:22.289 MyApp[4566:503] -[__NSCFConstantString
> unsignedLongLongValue]: unrecognized selector sent to instance 0x7fff7847da00
> 2011-12-16 22:45:22.297 MyApp[4566:503] (
> 0 CoreFoundation 0x00007fff8b121286
> __exceptionPreprocess + 198
> 1 libobjc.A.dylib 0x00007fff89b53d5e
> objc_exception_throw + 43
> 2 CoreFoundation 0x00007fff8b1ad4ce -[NSObject
> doesNotRecognizeSelector:] + 190
> 3 CoreFoundation 0x00007fff8b10e133
> ___forwarding___ + 371
> 4 CoreFoundation 0x00007fff8b10df48
> _CF_forwarding_prep_0 + 232
> 5 Foundation 0x00007fff939f4e7c
> _NSSetUnsignedLongLongValueForKeyWithMethod + 56
> 6 Foundation 0x00007fff939a3ded
> _NSSetUsingKeyValueSetter + 177
> 7 Foundation 0x00007fff939a38ad
> -[NSObject(NSKeyValueCoding) setValue:forKey:] + 400
> 8 Foundation 0x00007fff939d5bb2
> -[NSObject(NSKeyValueCoding) setValue:forKeyPath:] + 349
> 9 AppKit 0x00007fff8bb6b33b -[NSBinder
> _setValue:forKeyPath:ofObject:mode:validateImmediately:raisesForNotApplicableKeys:error:]
> + 243
> 10 AppKit 0x00007fff8bb6aeaa -[NSBinder
> setValue:forBinding:error:] + 260
> 11 AppKit 0x00007fff8bf08ecb
> -[NSValueBinder
> _applyObjectValue:forBinding:canRecoverFromErrors:handleErrors:typeOfAlert:discardEditingCallback:otherCallback:callbackContextInfo:didRunAlert:]
> + 191
> 12 AppKit 0x00007fff8bf08b6f
> -[NSValueBinder
> applyDisplayedValueHandleErrors:typeOfAlert:canRecoverFromErrors:discardEditingCallback:otherCallback:callbackContextInfo:didRunAlert:error:]
> + 591
> 13 AppKit 0x00007fff8bf08902
> -[NSValueBinder
> _applyDisplayedValueIfHasUncommittedChangesWithHandleErrors:typeOfAlert:discardEditingCallback:otherCallback:callbackContextInfo:didRunAlert:error:]
> + 154
> 14 AppKit 0x00007fff8bf07f78
> -[NSValueBinder
> validateAndCommitValueInEditor:editingIsEnding:errorUserInterfaceHandled:] +
> 488
> 15 AppKit 0x00007fff8bf4837d
> -[_NSBindingAdaptor
> _validateAndCommitValueInEditor:editingIsEnding:errorUserInterfaceHandled:bindingAdaptor:]
> + 183
> 16 AppKit 0x00007fff8bf48488
> -[_NSBindingAdaptor
> validateAndCommitValueInEditor:editingIsEnding:errorUserInterfaceHandled:] +
> 256
> 17 AppKit 0x00007fff8be62cc5
> -[NSTextField textDidChange:] + 187
> 18 Foundation 0x00007fff9397cde2
> __-[NSNotificationCenter addObserver:selector:name:object:]_block_invoke_1 +
> 47
> 19 CoreFoundation 0x00007fff8b0c9e0a
> _CFXNotificationPost + 2634
> 20 Foundation 0x00007fff93969097
> -[NSNotificationCenter postNotificationName:object:userInfo:] + 65
> 21 AppKit 0x00007fff8bec4130
> -[NSTextView(NSSharing) didChangeText] + 348
> 22 AppKit 0x00007fff8bebe778
> _NSDoUserReplaceForCharRange + 484
> 23 AppKit 0x00007fff8bebe7e3
> _NSDoUserDeleteForCharRange + 40
> 24 AppKit 0x00007fff8beabd64
> -[NSTextView(NSKeyBindingCommands) deleteBackward:] + 441
> 25 CoreFoundation 0x00007fff8b110a1d -[NSObject
> performSelector:withObject:] + 61
> 26 AppKit 0x00007fff8bdb8bad
> -[NSResponder doCommandBySelector:] + 62
> 27 AppKit 0x00007fff8be9390e -[NSTextView
> doCommandBySelector:] + 198
> 28 AppKit 0x00007fff8bcecfff
> -[NSKeyBindingManager(NSKeyBindingManager_MultiClients)
> interpretEventAsCommand:forClient:] + 1799
> 29 AppKit 0x00007fff8c03eb4a
> -[NSTextInputContext handleEvent:] + 747
> 30 AppKit 0x00007fff8bf0aeaf -[NSView
> interpretKeyEvents:] + 248
> 31 AppKit 0x00007fff8be83c65 -[NSTextView
> keyDown:] + 691
> 32 AppKit 0x00007fff8b963544 -[NSWindow
> sendEvent:] + 7430
> 33 AppKit 0x00007fff8b8fb68f
> -[NSApplication sendEvent:] + 5593
> 34 AppKit 0x00007fff8b891682
> -[NSApplication run] + 555
> 35 AppKit 0x00007fff8bb1080c
> NSApplicationMain + 867
> 36 Spectrum 0x000000010882a1c2 main + 34
> 37 Spectrum 0x000000010882a194 start + 52
> 38 ??? 0x0000000000000003 0x0 + 3
> )
>
>
>
>
>
>
> _______________________________________________
>
> Cocoa-dev mailing list ([email protected])
>
> Please do not post admin requests or moderator comments to the list.
> Contact the moderators at cocoa-dev-admins(at)lists.apple.com
>
> Help/Unsubscribe/Update your Subscription:
> http://lists.apple.com/mailman/options/cocoa-dev/magnard%40web.de
>
> This email sent to [email protected]
>
_______________________________________________
Cocoa-dev mailing list ([email protected])
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com
This email sent to [email protected]