NSNumberFormatter doesn't allow typing decimal numbers - cocoa

I am totally bewildered using NSNumberFormatter. This should be totally simple but I can't get it to work.
I'd like to set an NSTextField to allow typing decimal numbers, either with a decimal point or without. Here is what I'd think would work:
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
[formatter setMinimumFractionDigits:0];
[formatter setMaximumFractionDigits:4];
[formatter setAllowsFloats:YES];
[[timeFlowMultiplierTF cell] setFormatter:formatter];
However, when typing in the textfield, pressing the "period" key for the decimal point doesn't yield one. Typing "3.14" give me "314". Throwing in [formatter setAlwaysShowsDecimalSeparator:YES] will initially format the number correctly, but if I type over it, I once again cannot type the decimal point.
What am I missing here? You would think this would be really simple

I realize this is about 4 years too late, but I just ran into this same nonsense and thought I'd share what the problem is (or could be), for posterity.
It turns out that all of the value accessors of NSTextField (-objectValue, -stringValue, -doubleValue, and so on) all end up calling -validateEditing. -validateEditing, in turn, uses the attached NSFormatter to convert the edited text into an object value, and then resets the text in the field with the reformatted value.
So if you have any code that watches the field as the user edits it and you "peek" at the value in the field, you are inadvertently reformatting and resetting the text in the text field.
It's not that the text field won't let you type a period; it's that is the text field already has "3" in it and when you type a period the text changes to "3.". If you then have an action/notification/delegate method that runs whenever something in the field changes, and you call any of the -typeValue methods, the "3." get formatted as "3" and it updates the cell, erasing the period you just typed.
My hack was to avoid the -typeValue methods and peek into the NSText object to get the edited text directly, without triggering -validateEditing:
// some method that runs every time the field changes...
NSTextField* valueField = self.valueField;
NSNumberFormatter* fieldFormatter = valueField.formatter;
NSText* fieldEditor = valueField.currentEditor;
id newValue = ( fieldEditor!=nil ? [fieldFormatter numberFromString:fieldEditor.string] : valueField.objectValue );

Thanks to and following on from #James Bucanek's answer: here is a Swift implementation that I've used when I was over-riding controlTextDidChange delegation method which unblocked the user from typing a decimal point. It also updates the enabled flag of a button on the interface according to if there's a valid (i.e > zero and non-zero length string) entry:
override func controlTextDidChange(notification: NSNotification) {
if let formatter: NSNumberFormatter? = self.user_textfield.formatter as? NSNumberFormatter {
if let field_editor: NSText = self.user_textfield.currentEditor() {
if let new_value: Float? = formatter!.numberFromString(field_editor.string!)?.floatValue {
self.my_button_out.enabled = new_value > 0.0
} else {
self.my_button_out.enabled = false
}
}
}
}

Related

NSTextView changing Insertion Point (Caret) Cursor size when there is no string

I am creating a NSTextView, and I want to change the size of the insertion point when the NSTextView is empty.
It appears that the caret cursor (I-Beam / IBeam cursor) size is based off of the current font size attribute on the attributedString property of the NSTextView.
The problem is that because the NSTextView has an empty attributedString, there can be no attributes on the string. This means that I can't apply a font size.
However, I can change the typingAttributes to have the fontSize be correct-- but the Insertion Point Cursor does not update size until I start typing
See these screenshots:
Notice here the textview on the right has a small insertion point cursor:
I start typing, and it updates in size:
So my question is, is there a property that I need to set on the NSTextView when I initialize it, in order to get the initial Insertion Point Cursor the correct Size? (I want it to match the typingAttributes font)
EDIT:
#Mark Bessey brought up a good question-- what is the order that I'm setting the typing attributes:
[_textEditor setTypingAttributes:typingAttributes];
[_textEditor setDelegate:self];
[view addSubview:_textEditor];
[self updateInsertionPointColor];
[[view window] makeFirstResponder:_textEditor];
[view setNeedsDisplay:YES];
I just ran into the same problem and – after not finding a good solution anywhere – managed to force the NSTextView to update its pointer by inserting a character and then replacing that character with an empty string in the same cycle.
I'm doing this in textView(_:shouldChangeTextIn:replacementString:), but I guess it works elsewhere just as fine.
let attributedString = NSAttributedString(string: " ", attributes: attributes)
let range = someRange
let insertLocation = range.location + range.length
textView.textStorage?.insert(attributedString, at: insertLocation)
textView.typingAttributes = attributes
textView.replaceCharacters(in: range, with: "")
Doing this works in a simple test app, in my applicationDidFinishLaunching method:
NSFont *font = [NSFont fontWithName:#"Marker Felt" size:24.0];
NSDictionary *attr = #{
NSFontAttributeName: font
};
[self.textView setTypingAttributes:attr];
I get the large I-beam cursor that I'd expect.
This is basically the same as what TextEdit does (sample code here), so it doesn't look like you're doing anything wrong, as far as setting the typingAttributes goes.
I wonder if perhaps you're setting the attributes at the wrong time. When do you set the typingAttributes? Is the view on-screen? Is it the first responder?
To changed the attributes when there is no text, using typingAttributes.
Typing attributes are reset automatically whenever the selection changes. However, if you add any user actions that change text attributes, the action should use this method to apply those attributes afterwards. User actions that change attributes should always set the typing attributes because there might not be a subsequent change in selection before the next typing.
#IBOutlet var targetTextView: NSTextView!{
didSet {
let attributes:[NSAttributedString.Key:Any] = [
.font:NSFont.userFont(ofSize: 16.0) ?? NSFont.systemFont(ofSize: 16.0),
.foregroundColor:NSColor(named: "targetColor")!
]
targetTextView.typingAttributes = attributes
}
}

xcode How to show $ value in a Label and a Text Box. Example 125000 as 125,000.00

I need to show $value on a Label.
currently it appears as 125000 but i need it to be 125,000.00
thanks in advance to all supporters.
The solution of your problem is NSNumberFormatter
Some code to get you started:
NSNumberFormatter *currencyFormatter = [[NSNumberFormatter alloc] init];
[currencyFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
NSLog(#"%#", [currencyFormatter stringFromNumber:[NSNumber numberWithInt:10395209]]);
[currencyFormatter release];
Drag the Number Formatter (found in the object library) object onto the field/label. Change the behavior (be sure your in the attributes inspector for the number formatter) to 'OS X 10.4+ Custom' (that's what it was in Xcode 4.5.2).
In the 'Integer Digits' field, change the minimum to 1 and leave the maximum whatever you need. For the 'Fraction Digits' fields set the minimum and maximum to 2.
Near the top of the field, stick a dollar sign in front of the 'Format (+)' and '(-)' fields.
Check the group separator box then change the primary and secondary grouping fields to 3.

Adding TextFields with Commas Xcode

I am trying to add the values in two textfields. When value is inputed into both and a button is pressed to add it, the text fields are formatted to have commas:
NSNumberFormatter *formatter = [[NSNumberFormatter alloc]init];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
carPriceField1.text = [formatter stringFromNumber: [formatter numberFromString:carPriceField1.text]];
so then if you click the button again, the commas are still inside of the textfield. So the calculation ignores all the numbers after the comma.
for example, if it were to add 20,000 and 30,000 the answer would be 50.
How can I add these numbers with commas? Do I have to reformat, and then add?
NSNumberFormatter is not intended to be used this way. In Interface Builder just drag a Number Formatter onto the text field you want to be formatted with commas. Then it will format the text it displays in the text field according to the settings you provide in Interface Builder. When you want to retrieve the numerical value just get it as follows for example if you want integers:
int carPrice = [carPriceField1 intValue];

Display an empty field when NSTextField contents are 0

I have an NSTextField which holds numeric (integer) values. There is a number formatter attached to the field. I want to display an empty field when the value of the field is set to 0 (zero). I think I tried every combination with the formatter, without luck so far. Can this be done?
I don't think you can do that with NSNumberFormatter. But you could make a subclass and implement stringFromNumber: something like this:
-(NSString *)stringFromNumber:(NSNumber *)number {
NSNumber *zero = [NSNumber numberWithInt:0];
if ([number isEqualToNumber:zero])
return #"";
else
return [super stringFromNumber:number];
}
EDIT:
I was wrong. You can do that with NSNumberFormatter:
[formatter setZeroSymbol:#""];

Cocoa Text - refreshing text on-the-fly

In an app I'm working on, the user inputs plain text, and the app reformats the text by transforming it to an NSAttributedString, and displays it. This all happens live.
Currently, I'm doing the following on my NSTextView's textDidChange delegate method:
- (void)textDidChange:(NSNotification *)notification {
// saving the cursor position
NSInteger insertionPoint = [[[self.mainTextView selectedRanges] objectAtIndex:0] rangeValue].location;
// this grabs the text view's contact as plain text
[self updateContentFromTextView];
// this creates an attributed strings and displays it
[self updateTextViewFromContent];
// resetting the cursor position
self.mainTextView.selectedRange = NSMakeRange(insertionPoint, 0);
}
While this mostly works, it's not ideal. The text seems to blink for a split second (you especially notice it on the red dots under spelling errors), and when the cursor was previously near one of the edges of the visible rect, it the scroll position gets reset. In my case, this is a very much undesirable side-effect.
So my question is: Is there a better way of doing what I'm trying to do?
I think you have a slight misconception of how an NSTextView works. The user never enters a "plain string", the data store of an NSTextView is always an NSTextStorage object, which is a subclass of NSMutableAttributedString.
What you need to do is add/remove attributes to the existing attributed string that the user is editing, rather than replacing the entire string.
You should also not make changes to the string in the ‑textDidChange: delegate method, as changing the string from that method can cause another change notification.
Instead, you should implement the delegate method ‑textStorageDidProcessEditing:. This is called whenever the text changes. You can then make modifications to the string like so:
- (void)textStorageDidProcessEditing:(NSNotification*)notification
{
//get the text storage object from the notification
NSTextStorage* textStorage = [notification object];
//get the range of the entire run of text
NSRange aRange = NSMakeRange(0, [textStorage length]);
//for example purposes, change all the text to yellow
//remove existing coloring
[textStorage removeAttribute:NSForegroundColorAttributeName range:aRange];
//add new coloring
[textStorage addAttribute:NSForegroundColorAttributeName
value:[NSColor yellowColor]
range:aRange];
}

Resources