Messed up method - if/then structure and string manipulation problems - iphone

I have a method that gets called any time a control on my view changes and should update a UILabel. It has two UITextFields and two UISliders. First I check to see if either of the UITextFields are empty and if so, advise that they need to be filled in. Otherwise I get the difference between the UITextFields' values and generate a couple of floats to use in my NSStrings.
I get a warning that message is not used and I get an error about the NSStrings (can't remember now exactly what - I'm not at my Mac...)
And even when I chopped the messages down to something simple that worked, when delta == 0, it does the delta <= 0 message.
Oh, and the strings don't put the values in where the % signs are, they just print the % signs.
I've been hacking at this too long and need help...
- (void)updateAdvice {
if ([chlorineSourceField.text isEqualToString:#""] || [poolVolumeField.text isEqualToString:#""]) {
NSString *message = [[NSString alloc] initWithString:#"Enter a chlorine source and pool volume."];
}
else {
int delta = [targetLabel.text intValue] - [startingLabel.text intValue];
float chlorineAmount = delta * [poolVolumeField.text intValue] * chlorineConstant;
float percentRemove = (1 - ([targetLabel.text floatValue] / [startingLabel.text floatValue]));
float gallonsRemove = percentRemove * [poolVolumeField.text intValue];
if (delta == 0) {
NSString *message = [[NSString alloc] initWithString:#"No adjustments necessary. You're on target"];
}
if (delta >= 0) {
NSString *message = [[NSString alloc] initWithFormat:#"To increase FC by %dppm, add %3.1f oz of %#.", delta, chlorineAmount, chlorineSourceField.text];
}
if (delta <= 0) {
NSString *message = [[NSString alloc] initWithFormat:#"You're above target already. Replace %d%% or %d gallons of water - or just wait for it to come down.", percentRemove*100, gallonsRemove];
}
}
adviceLabel.text = message;
[message release];
}

You are missing the else part, thus all three if statements are being evaluated consecutively. If delta == 0, it will satisfy all three if statements. Thus, the last allocation will overwrite the previous two. (And you'll be leaking memory)
Also, your message variable is scoped as local to the if block it's declared into. You might want to move the message declaration at the function level scope.
As far as % not working, you are using the instance initializer initWithFormat with the syntax for the class initializer stringWithFormat. initWithFormat takes the parameters for the formatted string in a separate parameter - arguments:. (Btw, it also requires locale:)

As far as the message variable goes, it's scoped so it can't be used outside the if/else statement in which it is declared. You want to declare it before the if statement, so you can use it outside of the if statement. So something like this:
- (void)updateAdvice {
NSString *message = nil;
if ([chlorineSourceField.text isEqualToString:#""] || [poolVolumeField.text isEqualToString:#""]) {
message = [[NSString alloc] initWithString:#"Enter a chlorine source and pool volume."];
}
else {
int delta = [targetLabel.text intValue] - [startingLabel.text intValue];
float chlorineAmount = delta * [poolVolumeField.text intValue] * chlorineConstant;
float percentRemove = (1 - ([targetLabel.text floatValue] / [startingLabel.text floatValue]));
float gallonsRemove = percentRemove * [poolVolumeField.text intValue];
if (delta == 0) {
message = [[NSString alloc] initWithString:#"No adjustments necessary. You're on target"];
}
if (delta >= 0) {
message = [[NSString alloc] initWithFormat:#"To increase FC by %dppm, add %3.1f oz of %#.", delta, chlorineAmount, chlorineSourceField.text];
}
if (delta <= 0) {
message = [[NSString alloc] initWithFormat:#"You're above target already. Replace %d%% or %d gallons of water - or just wait for it to come down.", percentRemove*100, gallonsRemove];
}
}
adviceLabel.text = message;
[message release];
}

Related

Int decimal count in iPhone

I need to know whatever an int64_t has decimals, and how many. This should be placed in if-else-statement. I tried this code, but it causes the app to crash.
NSNumber *numValue = [NSNumber numberWithInt:testAnswer];
NSString *string = [numValue stringValue];
NSArray *stringComps = [string componentsSeparatedByString:#"."];
int64_t numberOfDecimalPlaces = [[stringComps objectAtIndex:1] length];
if (numberOfDecimalPlaces == 0) {
[self doSomething];
} else {
[self doSomethingElse];
}
Your question doesn't make a lot of sense; you are creating the NSNumber object from an int so it will never have decimal places, as an int cannot store them. The reason your code is crashing is that it assumes that the array of components is always at least 2 elements long (as you use objectAtIndex:1).
This is better, though still not that good:
NSString *answer = ...; // From somewhere
NSArray *stringComps = [answer componentsSeparatedByString:#"."];
if ([stringComps count] == 0) {
[self doSomething];
} else if [stringComps count] == 1) {
[self doSomethingElse];
} else {
// Error! More than one period entered
}
This still isn't a very good test as it only tests if a period (.) has been entered, not a valid number.

I am getting Expected identifier in Xcode

I'm making an app for iPhone using obj-c that finds side lengths and angles for triangles. Part of the app uses the Pythagorean Theorem.
NSNumber *pySidea = [NSNumber numberWithInteger:[[sideA text] integerValue]];
NSNumber *pySideb = [NSNumber numberWithInteger:[[sideB text] integerValue]];
NSNumber *pySidec = [NSNumber numberWithInteger:[[sideC text] integerValue]];
int pyAside = [pySidea intValue];
int pyBside = [pySideb intValue];
int pyCside = [pySidec intValue];
if ([aSide length] = 0) {
NSString *finalAnserc = [sqrtf(powf(pyAside, 2) + powf(pyBside, 2))];
sideCstring = #"_anserSidec";
}
sideA, sideB and sideC are the sides of a triangle using a text field. I don't get an error for any part except
if ([aSide length] = 0) {
NSString *finalAnserc = [sqrtf(powf(pyAside, 2) + powf(pyBside, 2))];
sideCstring = #"_anserSidec";
}
where I get "Expected identifier". Thanks for any help.
This line:
NSString *finalAnserc = [sqrtf(powf(pyAside, 2) + powf(pyBside, 2))];
Seems to be wanting to create a string, but doesn't do anything except some arithmetic inside square brackets, which isn't valid syntax. I think you want something like:
float answer = sqrtf(powf(pyAside, 2) + powf(pyBside, 2));
NSString *finalAnswer = [[NSNumber numberWithFloat:answer] stringValue];
Calling powf() to square a number is a bit heavy-handed, too. You could just write:
float answer = sqrtf(pyAside*pyAside + pyBside*pyBside);
As Nithin notes in his answer, you have a logical error with your if statement too - you want to be using == probably.
Check your 'if' condition , it should be if([aSide length] == 0)
Remember two equal (=) signs...

Spotted a leak in UITextView delegate method. Confused about solution

I've got a problem with an UITextView and one of its delegate methods in my navigation based app:
- (BOOL)textView:aView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
I managed to limit the max length of text the user can input using the above method. But I'm using a leaky array for that matter I think.
The problem is:
I want to save the amount of typed characters right in the very moment the user enters the last line of my textview. I then use that value to calculate the string length - which I compare to the textview's content size to set a limit. The code works fine - but since the method it's inside of is updating with every text input, I'm having trouble releasing the array in the right moment.
Here's some code:
if (numLines == 9)
{
if (!numCharsArray)
{
numCharsArray = [[NSMutableArray alloc] initWithCapacity:1]; // Stack trace gives this line 3,3% of the leak.
}
numChars = tView.text.length;
NSNumber *number = [[NSNumber alloc] initWithInteger:numChars]; // This line gets 77,3%.
[numCharsArray addObject:number]; // This line gets the rest, 24,3%.
[number release];
startChars = [[numCharsArray objectAtIndex:0] integerValue];
NSString *lastLine = [[NSString alloc]initWithString:[[tView text] substringFromIndex:startChars]];
CGSize lineSize = [lastLine sizeWithFont:tView.font forWidth:tView.contentSize.width lineBreakMode:UILineBreakModeWordWrap];
[lastLine release];
if (range.length > text.length)
{
return YES;
}
else if (numLines == 9 && lineSize.width >= tView.contentSize.width - 45)
{
return NO;
}
}
else
{
numCharsArray = nil;
/*
if(numCharsArray)
{
[numCharsArray release];
}
*/
}
I tried the out-commented statement above, but that gives me an app crash once I leave the last line of the textview. And as you can see in the code comments - without releasing the array I get a leak.
So how and where do I release that array correctly - keeping it safe while the user is on the last line?
Just replace with
first one
numCharsArray = [NSMutableArray array]; // you do not need to release
//explicitly as its autorelease numberWithInt
second one
NSNumber *number = [NSNumber numberWithInt:numChars]; //autorelease
NSString *lastLine = [[tView text] substringFromIndex:startChars];

Message Sent To Deallocated Instance ... As far as I can tell, there's no reason it should be deallocated

I am working on an Iphone App that uses a SQLite database as its main source of Model data.
When the app opens, the singleton object called "Model" scans the SQLite table, uses each row to create a "Result" object (an NSObject subclass), and then adds each Result to an NSMutableArray.
Here's the code for that section, starting right after I have found the path for the database:
- (void)populateResultArrayWith:(NSString *)dbPath {
const char *sql = "SELECT * FROM results";
sqlite3_stmt *selectstmt;
if(sqlite3_prepare_v2(database, sql, -1, &selectstmt, NULL) == SQLITE_OK) {
while(sqlite3_step(selectstmt) == SQLITE_ROW) {
NSInteger pkInt = sqlite3_column_int(selectstmt, 0);
NSNumber *pk = [NSNumber numberWithInt:pkInt];
NSString *v = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 1)];
NSString *n = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 2)];
NSString *s = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 3)];
NSNumber *c = [NSNumber numberWithFloat:(float)sqlite3_column_double(selectstmt, 4)];
NSString *t1 = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 5)];
NSString *t2 = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 6)];
NSString *t3 = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 7)];
Result *resultObj = [[Result alloc] initWithPrimaryKey:(NSNumber *)pk
withVerb:(NSString *)v
withNoun:(NSString *)n
withSuffix:(NSString *)s
withCost:(NSNumber *)c
withTag1:(NSString *)t1
withTag2:(NSString *)t2
andTag3:(NSString *)t3];
[self.resultArray addObject:resultObj];
[resultObj release];
}
}
else
sqlite3_close(database); //Even though the open call failed, close the database connection to release all the memory.
NSLog(#"Model: Closing Database");
}
Later, one of my view controllers tells my Model to start a repeating NSTimer, and triggers a method to generate a random result from the NSMutableArray:
-(void)startTimer
{
if (startDate && startDate != nil) {
startDate = nil;
}
self.modelCumulativeValueWasted = 0;
startDate = [[NSDate date] retain];
modelTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0/1.0)
target:self
selector:#selector(tick)
userInfo:nil
repeats:YES];
[self randomResult];
}
And:
-(Result *)randomResult
{
if (randomResult != nil) {
randomResult=nil;
}
NSLog(#"Selected Result Retain Count At Beginning of Method = %i", [randomResult retainCount]);
// Generate a random int between 0 and the total count of the resultArray.
int randomIndex = arc4random() % [resultArray count];
// Select a result from the resultArray at that index - with an iVar.
randomResult = [resultArray objectAtIndex:randomIndex];
NSLog(#"Selected Result Retain Count At End of Method= %i", [randomResult retainCount]);
return randomResult;
}
The console read-out tells me that the retain count is normal during the course of this method... randomResult starts with a retain count of 0 after the opening if/then statement, and a retain count of 1 after it has been returned.
Then my Model applies some calculations based on other model data to generate a formatted Result string for display in the view:
-(void)calculateQuantity
{
// Get the float of the randomly selected result's "cost" property.
selectedCostFloat = [randomResult.cost floatValue];
// Calculate "how many of this result item could we buy" by dividing the cumulativeValueWasted by the costFloat.
float quantityFloat = (modelCumulativeValueWasted / selectedCostFloat);
NSLog(#"Quantity Float = %f", quantityFloat);
// Save this float as an NSNumber object, so a NSNumberFormatter can interpret it in ResultVC.
quantityNumber = [NSNumber numberWithFloat:quantityFloat];
}
My problem is that every time this method is called, and my model tries to get the floatValue of the NSNumber *cost property, I get a crash (using breakpoints, I isolated the first line of calculateQuantity: as the crash-point), and I get the following message in the console:
-[CFNumber floatValue]: message sent to deallocated instance 0x6332280
The way this works is that [sharedModel calculateQuantity]; is being called on the first tick of the timer, and then it crashes:
-(void)tick
{
// For "Time" every tick
NSDate *currentDate = [NSDate date];
NSTimeInterval countInSeconds = [currentDate timeIntervalSinceDate:startDate];
[df setDateFormat:#"HH:mm:ss"];
[df setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
NSDate *modelTimerDate = [NSDate dateWithTimeIntervalSinceReferenceDate:countInSeconds];
self.modelTimeString = [df stringFromDate:modelTimerDate];
NSLog(#"Time String: %#",self.modelTimeString);
//For "$" every tick
self.modelCumulativeValueWasted += self.modelMeetingValuePerSecond;
self.modelNumberToDisplayString = [NSString stringWithFormat:#"$%1.2f",self.modelCumulativeValueWasted];
NSLog(#"Cumulative Money Wasted: %#",self.modelNumberToDisplayString);
[self calculateQuantity];
}
I don't believe I am calling anything twice, and my memory management looks fine.
The most confusing part is that if I move the first couple lines of the calculateQuantity method to the end of the randomResult method, I still get the crash. Even after the console JUST told me that the retain count was 1, it immediately says I am trying to send a message to a deallocated instance.
I know this looks like a question that comes up a lot, but I haven't been able to figure out the problem from any of the other threads.
First, never rely on the -retainCount since the system may do some magics on it.
Second, the randomResult is return from resultArray, so the randomResult's owner is resultArray, you don't know when the owner release its member. To own a object by yourself, you should send it a -retain message, so change it to
-(Result *)randomResult
{
...
randomResult = [resultArray objectAtIndex:randomIndex];
return [[randomResult retain] autorelease];
}
Should be OK. Give it a try.

Change UILabel in loop

I want to change the UILabel after 2 sec in a loop.
But current code change it to last value after loop finished.
- (IBAction) start:(id)sender{
for (int i=0; i<3; i++) {
NSString *tempStr = [[NSString alloc] initWithFormat:#"%s", #" "];
int randomNumber = 1+ arc4random() %(3);
if (randomNumber == 1) {
tempStr = #"Red";
}else if (randomNumber == 2) {
tempStr = #"Blue";
} else {
tempStr = #"Green";
}
NSLog(#"log: %# ", tempStr);
labelsText.text = tempStr;
[tempStr release];
sleep(2);
}
}
Your code updates label to last value only as your function blocks main thread so UI cannot get updated. To solve that problem move your updating code to separate function and call it using performSelector:withObject:afterDelay: method. (or schedule calls using NSTimer)
Possible solution (you will also need to handle the case when user taps your button several times in a row, but that should not be too difficult):
- (IBAction) start:(id)sender{
[self updateLabel];
}
- (void) updateLabel{
static const NSString* allStrings[] = {#"Red", #"Blue", #"Green"};
static int count = 0;
int randomNumber = arc4random()%3;
NSString *tempStr = allStrings[randomNumber];
NSLog(#"log: %# ", tempStr);
labelsText.text = tempStr;
++count;
if (count)
[self performSelector:#selector(updateLabel) withObject:nil afterDelay:2.0];
}
- (IBAction) start:(id)sender{
for (int i=0; i<3; i++) {
int randomNumber = 1+ arc4random() %(3);
NSString *tempStr = #"";
if (randomNumber == 1) {
tempStr = #"Red";
}else if (randomNumber == 2) {
tempStr = #"Blue";
} else {
tempStr = #"Green";
}
[labelsText performSelector:#selector(setText:) withObject:tempStr afterDelay:i * 2]
NSLog(#"log: %# ", tempStr);
}
}
Don't use sleep() to perform actions after a delay, it blocks the whole thread. Use performSelector:withObject:afterDelay: instead. As you are probably running this on the main thread, sleep() will block any UI updates until after the whole loop has run, so the only thing you see is the last update. As a general rule of thumb, assume that the UI doesn't ever get updated until after the app has finished executing your method.
You shouldn't use %s as the format specifier for an NSString, that's the format specifier for a C string. You should use %# instead. In fact, if all you are doing is initialising the string with an NSString literal, there's no need to use initWithFormat at all, you can just use the literal itself.
You've also got big memory problems. At the beginning of the loop, you allocate memory for an instance of NSString that is a single space. You then overwrite the pointer to this memory when you assign to tempStr again, meaning you leak the original allocation of memory. Build and Analyze will find problems like this for you. Then you release tempStr, but as the second assignment to this pointer variable was to an autoreleased NSString, the instance will be released one time too many when the autorelease pool gets drained, which will probably manifest itself as a crash that's impossible to debug a little later in the app.
I'd do something like this:
- (void)showRandomColourAfterDelay {
static NSUInteger count = 0;
switch (arc4random()%3) {
case 0:
labelsText.text = #"Red";
case 1:
labelsText.text = #"Blue";
case 2:
labelsText.text = #"Green";
}
count++;
if (count >= 3) return;
[self performSelector:#selector(showRandomColourAfterDelay) withObject:nil afterDelay:3];
}
In fact, I'd probable use an NSArray to hold the colour strings, but that would involve changes outside of a single method, so I've stuck with your hard-coding approach.