I have a modal view in which a countdown starts when the user had performed some action. At the end of the countdown, the modal view would close. The following is the procedure I have written towards that goal but unfortunately, it is causing some thread problems. Is there any way to rewrite this in a way that does not have potential thread issues?
- (void)countDown {
static int i = 3;
if (i == 3) {
i--;
UIImage *three = [UIImage imageNamed:#"Three"];
countDownFlag = [[UIImageView alloc] initWithImage:three];
countDownFlag.frame = CGRectMake(0, 370, countDownFlag.frame.size.width, countDownFlag.frame.size.height);
countDownFlag.center = CGPointMake(width / 2, countDownFlag.center.y);
[self.view addSubview:countDownFlag];
[self performSelector:#selector(countDown) withObject:nil afterDelay:0.5];
} else if (i == 2) {
i--;
UIImage *two = [UIImage imageNamed:#"Two"];
[countDownFlag setImage:two];
[self performSelector:#selector(countDown) withObject:nil afterDelay:0.5];
} else if (i == 1) {
i--;
UIImage *one = [UIImage imageNamed:#"One"];
[countDownFlag setImage:one];
[self performSelector:#selector(countDown) withObject:nil afterDelay:0.5];
} else {
[self dismissViewControllerAnimated:YES completion:nil];
}
}
Edit: the picture shows what XCode tells me about the problem
More edit:
My code has changed to reflect vadian's answer. Here is the update
- (void)startCountDown {
NSLog(#"starting count down");
counter = 3;
[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(countDown:) userInfo:nil repeats:YES];
}
- (void)countDown:(NSTimer *)timer {
if (counter == 3) {
countDownFlag = [[UIImageView alloc] init];
countDownFlag.frame = CGRectMake(0, 370, countDownFlag.frame.size.width, countDownFlag.frame.size.height);
countDownFlag.center = CGPointMake(width / 2, countDownFlag.center.y);
dispatch_async(dispatch_get_main_queue(), ^{
[self.view addSubview:countDownFlag];
});
} else if (counter == 0) {
[timer invalidate];
[self dismissViewControllerAnimated:YES completion:nil];
return;
}
NSArray *imageNameArray = #[#"", #"One", #"Two", #"Three"];
UIImage *image = [UIImage imageNamed:imageNameArray[counter]];
dispatch_async(dispatch_get_main_queue(), ^{
[self.countDownFlag setImage:image];
});
counter--;
}
I new guess that the problem probably lies in the code that calls the countdown. Here is the code that does this.
- (void)changeLanguage:(BOOL) isMyanmar {
if (hasRun == YES) {
return;
}
hasRun = YES;
NSUserDefaults * userdefaults = [NSUserDefaults standardUserDefaults];
[userdefaults setBool:isMyanmar forKey:#"myanmar"];
[userdefaults synchronize];
[self updateCurrentLanguageText];
if ([self isMyanmar]) {
[self changeLanguageFlag:#"MyanmarFlagBig"];
} else {
[self changeLanguageFlag:#"UnitedKingdomFlagBig"];
}
//>>>>>>>>>>>>>>>>>>>> the problem is probably here
[self startCountDown];
}
Any code that runs after changing language code fails. The problem is probably there.
Edit: the thread problem is gone but the countdown isn't happening anymore.
After I have moved the code in changeLanguage into the methods that call it, the problem is mysteriously gone. (Repetitive but it works.)
- (void)changeLanguageToEnglish {
[theLock lock];
if (hasRun == YES) {
[theLock unlock];
return;
}
hasRun = YES;
[userdefaults setBool:false forKey:#"myanmar"];
[userdefaults synchronize];
[self updateCurrentLanguageText];
if ([self isMyanmar]) {
[self changeLanguageFlag:#"MyanmarFlagBig"];
} else {
[self changeLanguageFlag:#"UnitedKingdomFlagBig"];
}
[self startCountDown];
[theLock unlock];
}
- (void)changeLanguageToMyanmar {
[theLock lock];
if (hasRun == YES) {
[theLock unlock];
return;
}
hasRun = YES;
[userdefaults setBool:true forKey:#"myanmar"];
[userdefaults synchronize];
[self updateCurrentLanguageText];
if ([self isMyanmar]) {
[self changeLanguageFlag:#"MyanmarFlagBig"];
} else {
[self changeLanguageFlag:#"UnitedKingdomFlagBig"];
}
[self startCountDown];
[theLock unlock];
}
But the problem is that the countdown isn't happening anymore.
You could use an NSTimer to perform the countdown and a static variable for the counter.
The method startCountDown starts the timer.
The method countDown:(NSTimer *)timer is called every 0.5 seconds. The passed NSTimer argument is exactly the timer which has been started.
it performs the action according to its value and decrements the counter. If the counter is 0, the timer is invalidated and the controller dismissed.
static UInt8 counter = 0;
- (void)startCountDown {
countDownFlag = [[UIImageView alloc] init];
countDownFlag.frame = CGRectMake(0, 370, countDownFlag.frame.size.width, countDownFlag.frame.size.height);
countDownFlag.center = CGPointMake(width / 2, countDownFlag.center.y);
[self.view addSubview:countDownFlag];
counter = 3;
[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(countDown:) userInfo:nil repeats:YES];
}
- (void)countDown:(NSTimer *)timer {
if (counter == 0) {
[timer invalidate];
[self dismissViewControllerAnimated:YES completion:nil];
return;
}
NSArray *imageNameArray = #[#"", #"One", #"Two", #"Three"];
UIImage *image = [UIImage imageNamed:imageNameArray[counter]];
dispatch_async(dispatch_get_main_queue(), ^{
[self.countDownFlag setImage:image];
});
counter--;
}
Place static int i = 3; outside your method countDown, the way you did it it declares new variable every time you call that method.
Related
I am developing a learning app for preschool kids. This is my code:
numbersStoreArray = [[NSMutableArray alloc]initWithObjects:#"1.png",#"2.png",#"3.png",#"4.png",#"5.png",#"6.png",#"7.png",#"8.png",#"9.png",#"10.png", nil];
commonFunctionObject = [[SpeechCommonFunctions alloc]init];
commonFunctionObject.isRecordComplete = NO;
counter = 0;
isMicPresent = YES;
_confirmationPopupView.hidden = true;
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(repeatActionFire) userInfo:nil repeats:NO];
}
-(void)repeatActionFire
{
if(counter >= numbersStoreArray.count)
{
NSLog(#"finished");
[_numbersShowImageView removeFromSuperview];
[_speakerOrMicImageView removeFromSuperview];
UIImageView *congratzView = [[UIImageView alloc]initWithFrame:self.view.frame];
congratzView.image = [UIImage imageNamed:#""];
[self.view addSubview:congratzView];
}
else
{
[commonFunctionObject textToSpeechAction:numbersStoreArray :counter :_numbersShowImageView :_speakerOrMicImageView :isMicPresent];
[NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:#selector(ActionToCkeckRecordCompletion) userInfo:nil repeats:YES];
}
}
-(void)ActionToCkeckRecordCompletion
{
if(commonFunctionObject.isRecordComplete)
{
_confirmationPopupView.hidden = false;
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)playButtonAction:(id)sender
{
[commonFunctionObject recordPlayAction];
}
- (IBAction)nextButtonAction:(id)sender
{
counter+=1;
_confirmationPopupView.hidden = true;
commonFunctionObject.isRecordComplete = NO;
if(commonFunctionObject.player.playing){[commonFunctionObject.player stop];}
[self repeatActionFire];
}
- (IBAction)retryButtonAction:(id)sender
{
_confirmationPopupView.hidden = true;
commonFunctionObject.isRecordComplete = NO;
if(commonFunctionObject.player.playing){[commonFunctionObject.player stop];}
[self repeatActionFire];
}
After completion of this, this entry to the repeatActionFire and show finished. My question is: I need to display the congratulation view after the completion of the game and need to use two buttons.
Buttons for:
to reach the homepage
to retry the game.
How to do this?
I have a small sample project i'm using to figure out how to implement this on my main project. This simple project has 2 VC's both with segues to each other.
On the initial VC is a button which leads to the TimerVC (both using the same class).
The TimerVC has a button and a label. When the button is pressed the label will increase by 1 every second.
If the timer is on and I segue back to the initial VC and then to the TimerVC the timer continues but the label stops updating.
How can I keep the label updating? The timer keeps going in the back-end but once the segue happens the label stops updating.
EDIT: Code provided below. The Timer is also more complex to represent a little of what I'm trying to do.
VC.H
NSTimer * timer;
NSTimer * updateTimer;
#property (weak, nonatomic) IBOutlet UIButton *idleOutler;
- (IBAction)idleAttack:(id)sender;
VC.M
int enemy001Hp = 100;
int deadEnemy = NO;
int noHealth = 0;
bool enemy001Active = NO;
- (void) enemy1 {
enemy001Active = YES;
self.enemyHpLabel.text = [NSString stringWithFormat:#"%i", enemy001Hp];
}
- (void) enemyDamageTimer {
if (enemy001Active == YES) {
enemy001Hp -= 50;
self.enemyHpLabel.text = [NSString stringWithFormat:#"%i",enemy001Hp];
}
}
- (void) updateLabelTimer {
if (enemy001Active == YES) {
self.enemyHpLabel.text = [NSString stringWithFormat:#"%i", enemy001Hp];
}
}
- (void) stopTimer {
[timer invalidate];
timer = nil;
self.enemyHpLabel.text = [NSString stringWithFormat:#"0"];
}
- (void)viewDidLoad {
[super viewDidLoad];
[self enemy1];
if (enemy001Active) {
self.enemyHpLabel.text = [NSString stringWithFormat:#"%i", enemy001Hp];
} else if (enemy002Active == YES) {
self.enemyHpLabel.text = [NSString stringWithFormat:#"%i", enemy002Hp];
}
}
- (IBAction)idleAttack:(id)sender {
idleOn = YES;
self.idleOutler.hidden = YES;
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(enemyDamageTimer) userInfo:nil repeats:YES];
updateTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(updateLabelTimer) userInfo:nil repeats:YES];
if (enemy001Active == YES) {
if (enemy001Hp <= 0) {
[self enemy2];
enemy001Active = NO;
enemy002Active = YES;
}
} else if (enemy002Active == YES) {
if (enemy002Hp <= 0) {
self.enemyHpLabel.text = [NSString stringWithFormat:#"0"];
enemy002Hp = 0;
enemy002Active = NO;
[self stopTimer];
}
}
}
Pressing a button in my iOS app calls the changeLanguage method.
- (void)changeLanguage:(BOOL) isMyanmar {
if (hasRun == YES) {
return;
}
hasRun = YES;
NSUserDefaults * userdefaults = [NSUserDefaults standardUserDefaults];
[userdefaults setBool:isMyanmar forKey:#"myanmar"];
[userdefaults synchronize];
[self updateCurrentLanguageText];
if ([self isMyanmar]) {
[self changeLanguageFlag:#"MyanmarFlagBig"];
} else {
[self changeLanguageFlag:#"UnitedKingdomFlagBig"];
}
[self countDown];
}
- (void)countDown {
static int i = 3;
if (i == 3) {
i--;
UIImage *three = [UIImage imageNamed:#"Three"];
countDownFlag = [[UIImageView alloc] initWithImage:three];
countDownFlag.frame = CGRectMake(0, 370, countDownFlag.frame.size.width, countDownFlag.frame.size.height);
countDownFlag.center = CGPointMake(width / 2, countDownFlag.center.y);
[self.view addSubview:countDownFlag];
[self performSelector:#selector(countDown) withObject:nil afterDelay:0.5];
} else if (i == 2) {
i--;
UIImage *two = [UIImage imageNamed:#"Two"];
[countDownFlag setImage:two];
[self performSelector:#selector(countDown) withObject:nil afterDelay:0.5];
} else if (i == 1) {
i--;
UIImage *one = [UIImage imageNamed:#"One"];
[countDownFlag setImage:one];
[self performSelector:#selector(countDown) withObject:nil afterDelay:0.5];
} else {
[self dismissViewControllerAnimated:YES completion:nil];
}
}
The method used to run fine but recently is having trouble with threading at [self countDown].
What could be causing the problem and how can I resolve this?
Edit:
How changeLanguage gets called
Two methods call this method:
- (void)changeLanguageToEnglish {
[self changeLanguage:false];
}
- (void)changeLanguageToMyanmar {
[self changeLanguage:true];
}
How the button calls the methods:
[languageButton addTarget:self action:#selector(changeLanguageToEnglish) forControlEvents:UIControlEventTouchUpInside];
I have an NSTimer in another function but I want to be able to wait for the NSTimer to become invalidated before continuing on with the code is there a way to do this?
- (IBAction)addLifePoints:(UIButton *)sender
{
[[ARCHDuelCalculator sharedARCHDuelCalculator] setLifePointDelta:[NSNumber numberWithInt: [self.hiddenTextField.text intValue]]];
[[ARCHDuelCalculator sharedARCHDuelCalculator] setAddOrSubstract: YES];
[[ARCHDuelCalculator sharedARCHDuelCalculator] applyingDeltaToLifePointsByDelta];
// This will animate the life points
animationTimer = [NSTimer scheduledTimerWithTimeInterval:.01 target:self selector:#selector(animateLifePoints) userInfo:nil repeats:YES];
// This is where we bring it back to the view controller
self.duelistOneLifePoints.text = [[ARCHDuelCalculator sharedARCHDuelCalculator].duelistOneLifePoints stringValue];
self.duelistTwoLifePoints.text = [[ARCHDuelCalculator sharedARCHDuelCalculator].duelistTwoLifePoints stringValue];
self.hiddenTextField.text = #"";
[self syncTextField: self.hiddenTextField];
[self.hiddenTextField resignFirstResponder];
}
- (void) animateLifePoints
{
NSNumber *sections = [[ARCHDuelCalculator sharedARCHDuelCalculator] getLifePointSections];
for(int timer = 0; timer < 100; ++timer)
{
self.duelistOneLifePoints.text = [[[ARCHUtilities sharedARCHUtilities] subtractTwoNSNumbersByDataType:#"int" firstNumber:[NSNumber numberWithInt: [self.duelistOneLifePoints.text intValue]] secondNumber:sections] stringValue];
if ((timer % 14) == 0)
{
[self playLifePointSound:#"mainLifePointSound" typeOfFile:#"mp3"];
}
}
[animationTimer invalidate];
}
hope this works:
-split addLifePoints into 2 methods.
-put code after the nstimer in another method (newMethod)
-call newMethod right after the nstimer is invalidated.
- (IBAction)addLifePoints:(UIButton *)sender
{
[[ARCHDuelCalculator sharedARCHDuelCalculator] setLifePointDelta:[NSNumber numberWithInt: [self.hiddenTextField.text intValue]]];
[[ARCHDuelCalculator sharedARCHDuelCalculator] setAddOrSubstract: YES];
[[ARCHDuelCalculator sharedARCHDuelCalculator] applyingDeltaToLifePointsByDelta];
// This will animate the life points
animationTimer = [NSTimer scheduledTimerWithTimeInterval:.01 target:self selector:#selector(animateLifePoints) userInfo:nil repeats:YES];
}
- (void) animateLifePoints
{
NSNumber *sections = [[ARCHDuelCalculator sharedARCHDuelCalculator] getLifePointSections];
for(int timer = 0; timer < 100; ++timer)
{
self.duelistOneLifePoints.text = [[[ARCHUtilities sharedARCHUtilities] subtractTwoNSNumbersByDataType:#"int" firstNumber:[NSNumber numberWithInt: [self.duelistOneLifePoints.text intValue]] secondNumber:sections] stringValue];
if ((timer % 14) == 0)
{
[self playLifePointSound:#"mainLifePointSound" typeOfFile:#"mp3"];
}
}
[animationTimer invalidate];
[self newMethod]; //////////////////// ADD THIS LINE ALSO, continue code
}
-(void)newMethod{
//...so continue code...
// This is where we bring it back to the view controller
self.duelistOneLifePoints.text = [[ARCHDuelCalculator sharedARCHDuelCalculator].duelistOneLifePoints stringValue];
self.duelistTwoLifePoints.text = [[ARCHDuelCalculator sharedARCHDuelCalculator].duelistTwoLifePoints stringValue];
self.hiddenTextField.text = #"";
[self syncTextField: self.hiddenTextField];
[self.hiddenTextField resignFirstResponder];
}
Hi I'm trying to use NSTimer to create a countdown which and view this using a label. the problem is the label goes down in two's and i don't know why. any ideas?
heres my code
- (void)viewDidLoad
{
NSLog(#"%#", self.chosenTime);
[self startGame];
[super viewDidLoad];
NSString *timeString = self.chosenTime;
self.timer = [timeString intValue];
self.countdown.text = timeString;
// Do any additional setup after loading the view.
}
- (void)startGame
{
[self introCountdown];
[self performSelector:#selector(goAnimation) withObject:nil afterDelay:3.0];
NSString *timeString = self.chosenTime;
self.timer = [timeString intValue];
[self performSelector:#selector(startCountdown) withObject:nil afterDelay:4.0];
}
- (void)startCountdown
{
//self.countdownTimer = [NSTimer timerWithTimeInterval:1 target:self selector:#selector(decrementTimer:) userInfo:nil repeats:YES];
self.countdownTimer = [NSTimer scheduledTimerWithTimeInterval:1.00 target:self selector:#selector(decrementTimer:) userInfo:nil repeats:YES];
}
- (void)decrementTimer:(NSTimer *)timer
{
if(self.timer > 0)
{
self.timer = self.timer - 1;
self.countdown.text = [NSString stringWithFormat:#"%d", self.timer];
}
else
{
[self.countdownTimer invalidate];
self.countdownTimer = nil;
}
}
This code seems to be correct. May be you are changing label's text from somewhere else?
Sorry for the misinterpretation, the code is correct, it seems a UI problem or handling label from somewhere else, attach your source with xib's if possible