Change UILabel in loop - iphone

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.

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.

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];

endless "for" loop in release configuration of iPhone app

i have the following code in my application:
NSArray *lstAttributes = [conditionAttribute componentsSeparatedByString:cstrSystemUIConfigurationAttributeConditionSeparator],
*lstValues = [conditionValue componentsSeparatedByString:cstrSystemUIConfigurationAttributeConditionSeparator],
*lstValueTypes = [conditionValueType componentsSeparatedByString:cstrSystemUIConfigurationAttributeConditionSeparator];
if([lstAttributes count] != [lstValues count] ||
[lstAttributes count] != [lstValueTypes count]) return NO;
BOOL bResult = YES;
NSLog(#"attributes amount - %u", [lstAttributes count]);
for(uint i = 0; i < [lstAttributes count]; i ++)
{
NSLog(#"counter: %u", i);
SystemUIConfigurationAttributeCondition *condition = [SystemUIConfigurationAttributeCondition new];
condition.conditionAttribute = [lstAttributes objectAtIndex:i];
condition.conditionValue = [lstValues objectAtIndex:i];
condition.conditionValueType = [lstValueTypes objectAtIndex:i];
bResult &= [self checkCondition:condition forOwner:owner];
FreeObject(&condition);
if(!bResult) break;
}
return bResult;
everything is fine in "debug" configuration. but once i switch it to "release" i face the endless loop. console shows me the following: attributes amount - 2, counter: 0, counter: 1, counter: 1, counter: 1, counter: 1, counter: 1, counter: 1, ...... etc.
i tried to use different "for" and "while" operators, but nothing of that worked correctly. the loop still can't be stopped.
has anyone faced the same problem before?
Since you mention in your comments that it works if you comment out the &= operation, I'd change that line to:
bResult = bResult && [self checkCondition:condition forOwner:owner];
This does a boolean AND, which is what you really intend to do on this line anyway. &= does a bitwise AND, which isn't always equivalent to a boolean AND, since any nonzero value evaluates to true. For example:
BOOL a = 1;
BOOL b = 2;
if (a && b) {
NSLog (#"a && b is true"); // This line will execute
}
if (a & b) {
NSLog (#"a & b is true"); // This line won't execute
}
Since it's common to use object addresses in our conditionals, using the bitwise AND in place of a boolean AND could create bugs. For example, if you return an object address from getCondition:fromOwner:, intending it to mean YES, and that memory address happens to be an even number, then a bitwise AND with YES will evaluate to NO, where a boolean AND would evaluate to YES.
My best guess as to the cause of your specific bug is that the bitwise-AND is somehow causing a buffer overrun, which stomps on your i variable. If that hypothesis is correct, then switching to a boolean AND should fix that too.
workaround for this bug as promised:
at first i tried to use code blocks to enumerate all objects in a list instead of "for" loop.
__block BOOL bResult = YES;
[lstAttributes enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
SystemUIConfigurationAttributeCondition *condition = [SystemUIConfigurationAttributeCondition new];
condition.conditionAttribute = [lstAttributes objectAtIndex:idx];
condition.conditionValue = [lstValues objectAtIndex:idx];
condition.conditionValueType = [lstValueTypes objectAtIndex:idx];
bResult &= [self checkCondition:condition forOwner:owner];
FreeObject(&condition);
if(!bResult) *stop = YES;
}];
but i got a crash on second iteration. "lstValues" and "lstValueTypes" pointers suddenly changed their values and application received EXC_BAD_ACCESS. possibly usage of 3 arrays while enumerating only 1 of them is not a good idea. debugger shows that enumeration is performed on the same thread, but 2 of 3 arrays are corrupted by the moment of 2nd iteration.
so i decided to split up my initial loop into 2 parts:
prepare a list of conditions
check each condition.
first is a usual "for" loop, second - 'enumerateObjectsUsingBlock:" method of NSArray class. so the final code looks like:
NSArray *lstAttributes = [conditionAttribute componentsSeparatedByString:cstrSystemUIConfigurationAttributeConditionSeparator],
*lstValues = [conditionValue componentsSeparatedByString:cstrSystemUIConfigurationAttributeConditionSeparator],
*lstValueTypes = [conditionValueType componentsSeparatedByString:cstrSystemUIConfigurationAttributeConditionSeparator];
if([lstAttributes count] != [lstValues count] ||
[lstAttributes count] != [lstValueTypes count]) return NO;
NSMutableArray *lstConditions = [NSMutableArray new];
for(uint i = 0; i < [lstAttributes count]; i ++)
{
SystemUIConfigurationAttributeCondition *condition = [SystemUIConfigurationAttributeCondition new];
condition.conditionAttribute = [lstAttributes objectAtIndex:i];
condition.conditionValue = [lstValues objectAtIndex:i];
condition.conditionValueType = [lstValueTypes objectAtIndex:i];
[lstConditions addObject:condition];
FreeObject(&condition);
}
__block BOOL bResult = YES;
[lstConditions enumerateObjectsUsingBlock:^(id obj, NSUInteger i, BOOL *stop)
{
if([self checkCondition:[lstConditions objectAtIndex:i] forOwner:owner] == NO)
{
bResult = NO;
*stop = YES;
}
}];
FreeObject(&lstConditions);
return bResult;
this code works.
i would appreciate if anyone can explain the behavior of my initial loop.

Why doesn't this for loop execute?

I have a picker view controller to select a chemical source and possibly a concentration. If the source doesn't have concentrations, it just presents a single picker. It gets populated by an NSDictionary with source type names as keys and a custom model object I made called Chemical that has four properties, two NSString, one float and one BOOL.
When I trigger this with dictionary that has 2 components, I want to extract the four values from the Chemical that is represented. Note that I populate the picker with values from the first two properties, but not the float or BOOL. I run through the array for the key that's selected in the first component and check the string from the second component against the chemConcentration property from each of the Chemicals in the key/value array. When the chemConcentration matches, I know I have the right Chemical and I can get its properties to send back.
Whew!
The problem is that even though I know I get to the for loop, it seems to get skipped. The NSLog right before it prints, but the one inside doesn't. sourceConstant and sourceIsLiquid stay 0.0 and NO
- (IBAction)selectedSourceButton {
NSLog(#"selectedSourceButton pressed");
NSInteger sourceRow = [picker selectedRowInComponent:kSourceComponent];
NSString *selectedSource = [self.sources objectAtIndex:sourceRow];
NSArray *selectedChemicalGroup = [dictionaryOfSources objectForKey:selectedSource];
NSInteger concentrationRow = [picker selectedRowInComponent:kConcentrationComponent];
NSString *selectedConcentration = [[NSString alloc] init];
float selectedConstant = 0.0;
BOOL selectedIsLiquid = NO;
if (numberOfComponents == 2) {
NSLog(#"numberOfComponents = 2 if/then chosen"); // <-- This prints.
selectedConcentration = [self.concentrations objectAtIndex:concentrationRow];
NSLog(#"begin selectedConcentration for loop. Number of loops = %d", [selectedChemicalGroup count]); // <-- And so does this.
for (int i; i<[selectedChemicalGroup count]; i++) { // <-- But this doesn't seem to fire!
NSLog(#"selectedConcentration = %#, from selectedChemicalGroup = %#", selectedConcentration, [[selectedChemicalGroup objectAtIndex:i] chemConcentration]); // <-- Because this doesn't print.
if ([selectedConcentration isEqualToString:[[selectedChemicalGroup objectAtIndex:i] chemConcentration]]) {
selectedConstant = [[selectedChemicalGroup objectAtIndex:i] chemConstant];
selectedIsLiquid = [[selectedChemicalGroup objectAtIndex:i] chemIsLiquid];
}
}
}
else {
selectedConcentration = #"";
selectedConstant = [[selectedChemicalGroup objectAtIndex:0] chemConstant];
selectedIsLiquid = [[selectedChemicalGroup objectAtIndex:0] chemIsLiquid];
}
NSLog(#"selectedSourceButton source to return = %#, concentration = %#, sourceConstant = %1.7f, isLiquid = %d", selectedSource, selectedConcentration, selectedConstant, selectedIsLiquid);
if ([self.delegate respondsToSelector:#selector (sourcePickerViewController:didSelectSource:andConcentration:andConstant:andIsLiquid:)]) {
[self.delegate sourcePickerViewController:self didSelectSource:selectedSource andConcentration:selectedConcentration andConstant:selectedConstant andIsLiquid:selectedIsLiquid];
}
}
You need to initialize your variable i: for (int i = 0; ...
But there's a better way to do this, using "fast enumeration":
for (MyChemicalGroupClass *group in selectedChemicalGroup) {
if ([selectedConcentration isEqualToString:[group chemConcentration]]) {
...
}
}
Initialize loop count i
for (int i = 0; i<[selectedChemicalGroup count]; i++)
Do the following and you will understand why:
int i;
NSLog(#"%d", i);

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

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];
}