NSNumberformatter quasi Scientific notations - objective-c

I need the NSNumberFormatter to conditionally display very huge or very small numbers as Scientific notation. But if the number can be presented without, i need it to be only decimal.
6.62e-34 is Ok, but 4,2E1 is not.
Elsewhere in the system, I have solved this using the following method:
- (NSString *) formatNumber: (double) d {
BOOL sci=NO;
double dd = d;
if (dd<0.0) dd=(-d);
if ((dd>0.0) && (dd<1e-3)) sci=YES;
if (dd>1e+8) sci=YES;
if (sci) [nf setNumberStyle:NSNumberFormatterScientificStyle];
else [nf setNumberStyle:NSNumberFormatterDecimalStyle];
return [nf stringFromNumber:[NSNumber numberWithDouble:d]];
}
But at this particular place, the whole thing is set up in the interface builder, with bindings to an managed object modell and using an array controller object. In the first text cell of the colum in the tableview, I have dragged in the numberformatter. I dont know if its possible to define some custom methods to be called to do the formatting, instead of using this NumberFormatter. If I take it away, I got errors when I run it.
anyone have some idea ?

What you want to do is write a subclass of NSValueTransformer. You can then provide the name of that transformer class in IB, in the bindings inspector. Here is an example:
#implementation RDTransformer
- (id)init {
if (self = [super init]) {
self.formatter = [[NSNumberFormatter alloc] init];
[self.formatter setNumberStyle:NSNumberFormatterScientificStyle];
[self.formatter setMaximumFractionDigits:3];
}
return self;
}
+(Class)transformedValueClass {
return [NSString class];
}
+(BOOL)allowsReverseTransformation {
return NO;
}
-(id)transformedValue:(NSNumber *)value {
if ([value compare:#1000] == NSOrderedAscending) {
return value.stringValue;
}else{
return [self.formatter stringFromNumber:value];
}
}
In IB, the column (in a single column table) has its value bound to Array Controller.arrangedObjects with a value transformer of RDTransformer. I don't have any formatters connected to the table cells.

Related

Calculator label string displaying

I'm new to Objective-C and trying to make a calculator. My problem is I can't add the new number when button is pressed to the string no matter what I use (I tried methods appendString/Format and stringByAppendingString/Format).
Inputting numbers. I do the same for every other numbers:
- (IBAction)btn9:(id)sender {
[self tabbedNumber:9];
}
Declare the variable:
#property NSString *labelString;
Getting the number function:
-(void)tabbedNumber:(int)num{
NSString *lblStr = [[NSString alloc]init];
lblStr = [lblStr stringByAppendingString:[#(num) stringValue]];
self.labelString = lblStr;
[self updateText];
}
Displaying on calculator with a label calLabel:
- (void)updateText{
self.calLabel.text = self.labelString;
Best Anwser by #pavelTerziyski:
You keep lblStr = [lblStr stringByAppendingString:[#(num) stringValue]] in the function but the creation of the variable must be in the view didLoad - (void)viewDidLoad { [super viewDidLoad]; self.lblStr = [NSString new]; }
How you get the (int)num - because i tried your code with this and it worked perfectly fine
- (IBAction)buttonAction:(UIButton *)sender {
NSString *lblStr = [[NSString alloc]init];
lblStr = [lblStr stringByAppendingString:sender.titleLabel.text];
self.labelString = lblStr;
[self updateText];
}
- (void)updateText{
self.numberLabel.text = self.labelString;
}
Try this method 👇
- (IBAction)tabbedNumber:(UIButton *)sender{
self.labelString.text = [self.labelString.text stringByAppendingString:sender.titleLabel.text];
}
With this you can add any button's title label text to your label string.
But you should connect number buttons to this method.
if you want to update label with another method try this 👇
- (IBAction)tabbedNumber:(UIButton *)sender{
[self updateText:sender.titleLabel.text];
}
- (void)updateText:(NSString *)tappedNumberString{
self.labelString.text = [self.labelString.text stringByAppendingString:tappedNumberString];
}
Don't forget to connect tabbedNumber method to number Buttons.
Appending something to a new created string is not very useful.
You need to append the integer (as text) to the property labelString which seems to hold the actual value of the label.
-(void)tabbedNumber:(int)num{
self.labelString = [self.labelString stringByAppendingFormat:#"%i", num];
[self updateText];
}

NSMutableDictionary - entries appear to be over-written by each addition

I'm fairly new to Objective-C; but have been coding for years and this one really stumps me.
I'm trying to build an iPhone app and wanted to create a "settings" screen which will use a Table format. (Xcode 5.1.1).
I want to future proof the main Settings screen and make it easy for the application coding by hiding the "hard work" in subroutines/methods.
I may be getting too clever but I've created a class for each 'setting' that contains screen prompts, default values etc and using an Enum to cross-reference it (so the compiler will highlight typos etc)
The problem I'm encountering is that when I add entries to my NSMutableDictionary and use lldb to print the values; every entry seems to have the same "key" and values. I've tried converting the eNum to an NSNumber and also as an NSString -- no difference in the result - so I'm obviously doing something else daft but just can't see it
The following code is from various .m & .h files, I've omitted boring stuff that you always "have to have" to keep it short
// basic x-ref I want to use in my code
typedef NS_OPTIONS(NSInteger, ConfigurationType) {
unDefined = -1,
Server = 0,
Id = 1,
Phone = 2
};
// definition for a "single" Settings value
#interface SettingDefinition : NSObject
#end
#implementation SettingDefinition
ConfigurationType _cfgType;
NSString *_cfgName;
NSString *_screenTitle;
NSString *_value;
- (NSString *)description
{
NSString *className = NSStringFromClass([self class]);
return [NSString stringWithFormat:#"<%#: x%p Type=%d dbKey=%# '%#' -> %#>", className, self, _cfgType, _cfgName, _screenTitle, _value];
}
- (id)initType:(ConfigurationType)cfgOption
withDbKey: (NSString*)dbKey
asOptionTitle:(NSString*)cfgTitle
withValue:(NSString*)itmValue
{
self = [super init];
if (self) {
_screenTitle = cfgTitle;
_cfgName = dbKey;
_cfgType = cfgOption;
_value = itmValue;
}
return self;
}
#end
#interface Configuration : NSObject
#end
#implementation Configuration {
NSMutableDictionary *Settings; // List of Setting structures
};
- (id)init {
self = [super init];
if (self) {
Settings = [[NSMutableDictionary alloc]init];
[self add:Server withDbKey:#"Server" asOptionTitle:#"Server"];
[self add:Id withDbKey:#"Id" asOptionTitle:#"Your ID"];
[self add:Phone withDbKey:#"Phone" asOptionTitle:#"Phone No."];
}
return self;
}
- (void) add:(ConfigurationType)cfgOption
withDbKey:(NSString*)dbKey
asOptionTitle:(NSString*)cfgTitle
{
NSString * itmValue = [self configurationValue: cfgOption cfgName:dbKey];
SettingDefinition *x = [[SettingDefinition alloc]
initType: cfgOption
withDbKey: dbKey
asOptionTitle: cfgTitle
withValue: itmValue];
[Settings setObject:x forKey:[self asKey:cfgOption]];
}
- (NSString *) asKey:(ConfigurationType) settingType {
NSString *rc = [NSString stringWithFormat:#"%d", settingType];
return rc;
}
- (NSString *) configurationValue:(ConfigurationType) settingType {
// returns a suitable value from my system setup
// which is initially a null value until the user sets everything up
}
the debug window shows the following when I break after the final call to [self add: ...]
(lldb) po Settings
{
0 = "<SettingDefinition: x0x8e7c280 Type=2 dbKey=Phone 'Phone No.' -> (null)>";
1 = "<SettingDefinition: x0x8c703a0 Type=2 dbKey=Phone 'Phone No.' -> (null)>";
2 = "<SettingDefinition: x0x8e7c310 Type=2 dbKey=Phone 'Phone No.' -> (null)>";
}
The (null) is obviously due to no data in 'value' yet; but why do they all show as 'Phone'; if I break after the second call to [self add:..] they all show as 'Id'
UPDATE:
DOH! obviously they're globals (I've been using another IDE where everything is local until exposed) .. If I enclose them in braces in the implementation as the documentation states then the exhibited problem vanishes. I have properties to access the variables but as the setter does more than just set the memory, I thought I'd need my "own" variables to hold the data.. said it was something daft .. thank you!

What is the difference between STAssertEqualObjects and STAssertEquals?

I have the following class:
#import "Period.h"
#implementation Period
...
- (BOOL)isEqualTo:(id)object {
return [self isEqual:object];
}
- (BOOL)isEqual:(id)object {
if (object == self) {
return YES;
}
if ([[object beginDate] hash] == [[self beginDate] hash] &&
[[object endDate] hash] == [[self endDate] hash]) {
return YES;
}
return NO;
}
...
#end
And also the following test, written using OCUnit:
Period *period;
NSDate *beginDate;
NSDate *endDate;
- (void)setUp {
beginDate = [NSDate dateWithString:#"2011-02-25"];
endDate = [NSDate dateWithString:#"2011-03-25"];
period = [[Period alloc] initWithBeginDate:beginDate
endDate:endDate];
}
- (void)testEndDateShouldBeGreaterOrEqualThanBeginDate {
Period *newPeriod = [[Period alloc] initWithBeginDate:beginDate
endDate:beginDate];
STAssertEqualObjects(beginDate, [newPeriod beginDate], #"Begin dates are different");
STAssertEqualObjects(endDate, [newPeriod endDate], #"End dates are different");
}
Previously I was using STAssertEquals instead of STAssertEqualObjects and it was not calling isEqual method on Period.
I just want to understand two things:
What's the difference between those two methods?
What's the difference between isEqual and isEqualTo?
STAssertEquals compares the raw bytes that make up the two parameters that are passed to it, and is intended to be used with scalar types (float, int, char, etc.), structs or unions---you should not use it to compare Objective-C objects. STAssertEqualObjects compares two Objective-C objects by calling isEqual:.
isEqualTo: is used to support NSSpecifierTest (refer to the NSComparisonMethods Protocol Reference). It is unnecessary to provide an implementation for isEqualTo: if your objects aren't scriptable.

Cocoa binding: NSTextField with empty string for zero value

I have NSTextField with placeholder. And it's binded to some integer property. So I want to display empty text in the field (with placeholder shown) when binded integer is zero.
Is it possible to do it?
(Update)
I discovered that this can be done through NSNumberFormatter - it has —(void) setZeroSymbol: (NSString*) string method. Not tried yet this in practice...
You could use an NSValueTransformer.
(Just in case)Create a new class, subclass from NSValueTransformer. In the implementation, add something like this:
+(Class)transformedValueClass {
return [NSString class];
}
-(id)transformedValue:(id)value {
if (value == nil) {
return nil;
} else {
if ([value integerValue] == 0) {
return #"";
} else {
return [NSString stringWithFormat:#"%d", [value stringValue]];
}
}
}
In Interface Builder, select your field, go to the bindings tab, and in the Value Transformer drop down, either select or type in your class name you made. This should prevent you from having to worry about modifying it elsewhere. I'm not 100% positive about it showing the placeholder (I don't have a Mac available right now).
EDIT:
I can confirm that this does indeed work. Here is a link to a github project I made to show how to use it: https://github.com/macandyp/ZeroTransformer
check the integer value before binding, if you are binding at runtime. Try
int i;
if (i == 0)
{
txt.placeholder = #"text";
}
else
{
[txt setStringValue:[NSString stringWithFormat:#"%d",i]];
}
You can not do conditional binding.
You need to create another property that will hold the value based on condition and use that property and bind to textfield.
I am using bindedString and bindedInteger. bindedString is bound to text field.
Whenever some action is performed it is updated.
- (id)init{
self = [super init];
if (self) {
self.bindedString=#"place holder string";
}
return self;
}
- (IBAction)button:(id)sender {
if (self.bindedInteger==0) {
self.bindedString=#"place holder string";
}
else{
self.bindedString=[NSString stringWithFormat:#"%ld",self.bindedInteger];
}
}

How can I force NSTextField to only allow numbers?

In Interface Builder I’ve created a textfield and stuck an NSNumberFormatter into the cell. However, the user can still type text into the textfield. Is there any way to restrict the input to numbers only using interface builder? I thought that was the point of the NSNumberFormatter.
Every formatter has this method:
- (BOOL) getObjectValue: (id*) object forString: (NSString*) string
errorDescription: (NSError**) error;
This is valid for every formatter.
If the string is not valid it returns false and the object passed as argument (dereferenced) will be nil.
So instead of dropping a number formatter to the text field, use your own formatter as instance variable.Observe the text field implementing the delegate protocol so that at every change of the text you can be notified.
Then invoke this method:
NSNumber* number;
BOOL success=[formatter getObjectValue: &number forString: [myTextField stringValue] errorDescription:nil];
At this point if success is false (or check if number is nil), there is an invalid string in the text field, so do the action that is more appropriate for you (maybe delete the entire string, or display 0.0 as value).
There is also another method:
- (BOOL) isPartialStringValid : (NSString*) partialString: (NSString*) partialString
errorDescription: (NSString*) error;
This way you can know if a partial string is valid.For example with the scientific notation "1e" is not valid, but is partially valid because the user may add 1 and it will become "1e1".
Create an NSNumberFormatter subclass and put this in the implementation. In your code, set the YKKNumberFormatter as the formatter for the NSTextField/UITextField.
#implementation YKKNumberFormatter
- (BOOL)isPartialStringValid:(NSString *)partialString newEditingString:(NSString **)newString errorDescription:(NSString **)error {
// Make sure we clear newString and error to ensure old values aren't being used
if (newString) { *newString = nil;}
if (error) {*error = nil;}
static NSCharacterSet *nonDecimalCharacters = nil;
if (nonDecimalCharacters == nil) {
nonDecimalCharacters = [[[NSCharacterSet decimalDigitCharacterSet] invertedSet] retain];
}
if ([partialString length] == 0) {
return YES; // The empty string is okay (the user might just be deleting everything and starting over)
} else if ([partialString rangeOfCharacterFromSet:nonDecimalCharacters].location != NSNotFound) {
return NO; // Non-decimal characters aren't cool!
}
return YES;
}
#end