Start/stop button not working correctly iOS - ios

I got into a very noob problem which I can not seem to solve in any way, I am sure I am overlooking something, but cannot figure it out for gods sake!!
Okay, so I have a start/stop button in my app, it should resume the action if the action is paused and pause the action if it is already running. Here is my code:
- (IBAction)pause:(id)sender {
if(paused){
[self.progressBar resumeLayer];
paused = false;
NSLog(#"resume");
}
if(!paused){
[self.progressBar pauseLayer];
paused = true;
NSLog(#"pause");
}
}
The problem is: When i run the app and press on the button to pause, it works fine however after that it won't resume at all. After NSLogging to the console, I found that pause is called right after resume.... Why is this?? First: the button is clicked only once, how can it call two opposite methods? Second: Why isn't my BOOL check working?
EDIT: If I swap out one of the if statements to an else if, it works fine... Why is that?
Thank you!

Please Take Paused variable as BOOL & set with YES & NO

What is "paused"? A global variable? An instance variable? I don't know.
Convention is that instance variables should start with an underscore character, which they will do automatically if you just define a property. That way people have at least some idea what is happening.
Convention for BOOL values is YES or NO. Not true or false.
The real bummer is of course a stupid bug in your code. If paused == YES then the first if is executed, paused is set to NO, and then in the next test you check that paused == NO - which it is at this point.
You would have found that easily by stepping through the code in the debugger, line by line.

- (IBAction)pause:(id)sender {
if(paused){
[self.progressBar resumeLayer];
NSLog(#"resume");
}
if(!paused){
[self.progressBar pauseLayer];
NSLog(#"pause");
}
paused = !paused;
}

You should check for the other state in an else block instead of having two if statements.
The reason why this fails for you now is that, you change the state of paused to false and immediately after the first if statement is done you check for !paused. That will be true as you just changed it to false.
To fix it, you either can use an else if (!paused) statement or just a plain else if the paused variable is a boolean.
Something like this:
- (IBAction)pause:(id)sender {
if(paused){
[self.progressBar resumeLayer];
paused = false;
NSLog(#"resume");
} else {
[self.progressBar pauseLayer];
paused = true;
NSLog(#"pause");
}
}

1) Declare first BOOL variable in #interface ViewController ()
Ex. bool start;
2) Initialize BOOL variable in viewDidLoad method Like this start = YES;
3) now write code in button Action Like this
- (IBAction)btnPause:(UIButton *)btn {
if (start == YES)
{
btn.backgroundColor = [UIColor blackColor];
start = NO;
}
else
{
btn.backgroundColor = [UIColor whiteColor];
start = YES;
}
}
// Its change button background color but you can use your Logic.

Related

BOOL gives different result in if statement at update:(CFTimeInterval)currentTime method

I have two scenes - DifficultScene and GameScene. In DifficultScene I have three buttons - easy, medium and hard. I use a global variable Bool to keep track of the current difficulty level. When I try easy mode everything works fine, but when I try medium or hard, bool is changing every second, jumping from hard to medium and easy, making game unplayable. My question is - how can I fix it? Here are code were it happens:
GamesScene.m
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
extern BOOL isEasyMode;
extern BOOL isMediumMode;
extern BOOL isHardMode;
if ((isEasyMode = YES)) {
NSLog(#"easy");
[self computer];
}
if ((isMediumMode = YES)) {
NSLog(#"medium");
[self computerMedium];
}
if ((isHardMode = YES)) {
NSLog(#"hard");
[self computerHard];
}
[self scoreCount];
}
(if more code is needed, i will post it)
I think your update method calls periodically as per timer so it will get called continuously if it so then. Thats why it is happening i think and another major thing is you should use == for comparison. you are using (isEasyMode = YES) that means you are assigning YES to isEasyMode.
So replce all if statement like if ((isEasyMode = YES)) with if (isEasyMode == YES).
Update :
if statement should like,
if (isEasyMode == YES) {
NSLog(#"easy");
[self computer];
}
Hope this will help :)

Reactive Cocoa Conditional Delay

I'm trying to wrap my head around the ReactiveCocoa framework, but I'm stuck on trying to figure out how to delay conditionally.
For example, I want to set a CADisplayLink pause property to false when an array is empty. Here is how I accomplished this :
RACSignal *changeSignal = [self rac_valuesAndChangesForKeyPath:#keypath(self, projectiles) options:NSKeyValueObservingOptionNew observer:nil];
RAC(self.displayLink, paused) = [changeSignal map:^id(RACTuple *value) {
return #([((NSMutableArray *)value.first) count] == 0);
}];
But before I pause the display link, I want to keep animating for a few seconds, so I added a delay:2.5]; to the end of the map block.
Now I'm running into the problem that it's waiting 2.5 seconds to stop AND start the display link. I only want RAC to pause when I'm setting the self.displayLink.paused to YES but not when I'm setting it to NO.
Is this type of "conditional delay" possible in ReactiveCocoa, and if so, how is it done?
I got some help at the GitHub page for ReactiveCocoa :
You can use -flattenMap: to do this since it lets you return a signal instead of just a single value:
RAC(self.displayLink, paused) = [changeSignal flattenMap:^id(RACTuple *value) {
RACSignal *pauseSignal = [RACSignal return:#([((NSMutableArray *)value.first) count] == 0)];
if (pause) {
return [pauseSignal delay:2.5];
} else {
return pauseSignal;
}
}];
So when we're pausing, we delay 2.5 seconds and then pause. When we're unpausing we immediately send the value through

Unable to cancel NSTimer that executes blocks

I know that these NSTimer questions have come up numerous times, however since none seem to involve executing blocks that change the UI, I figured this is still an original question.
I have a subclass of UIButton that, for convenience sake (me, coming from an Android background), has an onClick and onHoldClick function. onClick simply takes a block and executes it in the selector that responds to UIControlEventTouchUpInside. The click function works great. For example:
[myButton setOnClick:^{
NSLog(#"clicked");
}];
The hold click functionality is not working so well.
[myButton setOnHoldClick:^{
NSLog(#"still holding click...");
}];
This listens for the UIControlEventTouchDown event, and performs a task after a delay:
- (void)clickDown:(id)sender
{
isClicked = YES;
[self performSelector:#selector(holdLoop:) withObject:nil afterDelay:delay];//For the sake of the example, delay is set to 0.5
}
The hold loop runs a repeated timer on another function, which handles the block execution (the timer variable is an NSTimer declared in the header file):
-(void)holdLoop:(id)sender
{
[self cancelTimers];
_timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(death:) userInfo:nil repeats:YES];
}
-(void)death:(id)_delay
{
if (isClicked)
{
_holdBlock();
}
else
{
[self cancelTimers];
}
}
The block that executes changes the value of a float, which is used to update the value of a label, which is then redrawn.
The first time the hold click event occurs, this works great. After that, it seems like timers don't get canceled, and new timers are still added. This is what my cancelTimers function looks like (calls here are retrieved from a collection of the other questions on this topic):
-(void)cancelTimers
{
[_timer invalidate];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(death:) object:nil];
}
What am I doing wrong, and how do I fix it?
Edit
I do, in fact, already have the function that responds to touch up inside:
- (void)clickUp:(id)sender
{
isClicked = NO;
[self cancelTimers];
_clickBlock();
}
Furthermore, I have realized that the issue comes from an unhandled cancel event. Is there a reason why iOS would auto-cancel my long press?
Solved
Since the block redrew the UI, it was also redrawing the buttons (and resetting their functionality). This event was causing a cancel event to be called on the button - which was not handled. Adding the following:
[self addTarget:self action:#selector(cancelClick:) forControlEvents:UIControlEventTouchCancel];
[self addTarget:self action:#selector(cancelClick:) forControlEvents:UIControlEventTouchUpOutside];
-(void)cancelClick:(id)sender
{
isClicked = NO;
[self cancelTimers];
}
As well as reconsidering what changes are made in the block, has gotten me past this issue.
As I understood from the comments and the code, the clickDown: is called for UIControlEventTouchDown so isClicked is set to YES when the first time the button is touched down. You need to add a selector to the event UIControlEventTouchUpInside. It's called when the user lifts his finger while being iside the bound of the button. Inside that method, set isClicked to NO.

How do I create a GLKViewController that starts in a paused state?

I create a GLKViewController like this:
// Create a GLK View Controller to handle animation timings
_glkVC = [[GLKViewController alloc] initWithNibName:nil bundle:nil];
_glkVC.preferredFramesPerSecond = 60;
_glkVC.view = self.glkView;
_glkVC.delegate = self;
_glkVC.paused = YES;
NSLog(#"initial state: %#", _glkVC.paused ? #"paused" : #"running");
but it immediately starts calling the delegate update method and the output from the NSLog above is: initial state: running
I am managing my view updates with setNeedsDisplay but I want the GLKViewController to handle animations from time to time so I want to unpause it only when needed. Is there a way to start the controller in a paused state?
have you tried pausing in the viewDidAppear method instead of the viewDidLoad method? It should look something like this:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// self.paused automatically set to NO in super's implementation
self.paused = YES;
}
Boom, done! If this works then you save an "if" check thousands of times a minute just to pause on launch!
The viewDidAppear method works for me, but not optimally. A few frames of visible animation occur before the pause takes effect. Using viewWillAppear worked much better:
- (void) viewWillAppear: (BOOL) animated
{
[ super viewDidAppear: animated ];
self.paused = YES;
}
In lieu of any answers I'm using this work-around:
I set .preferredFramesPerSecond = 1 initially and then in the update method I check if(preferredFramesPerSecond == 1) and set .paused = YES (and also set my real desired value for preferredFramesPerSecond). I can then allow the rest of the update method to run once after initialisation, or return immediately if I don't want it to run yet.
I then trigger redraws manually as needed with setNeedsDisplay and unpause it when I need it to animate.
If anyone has a better solution please answer as usual.
Have you tried overriding resumeOnDidBecomeActive to return NO? This should keep the animation paused on any activation, including the first.

How do you return a value inside an animation block?

I'm trying to return a value in a method that contains an animation block. The code I have below works perfectly fine, but it seems like there should be a better or cleaner way to do it. Is there a better way to do this?
+ (BOOL) flipImageAndTextForView:(UIView *) viewFlipImageAndText IsImageDisplayed:(BOOL) imageFlipDisplayed flipTextView:(UITextView *) textViewDonate flipImage: (UIImageView *)pictureImage{
__block BOOL tempBool;
[UIView transitionWithView:viewFlipImageAndText
duration:0.3f
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^(void) {
if (imageFlipDisplayed)
{
[viewFlipImageAndText bringSubviewToFront:textViewDonate];
tempBool = FALSE;
}
else
{
[viewFlipImageAndText bringSubviewToFront:pictureImage];
tempBool = TRUE;
}
}
completion:^(BOOL finished) {
NSLog(#"Done!");
}];
if(tempBool)
{
return TRUE;
}
else{
return FALSE;
}
}
Your code has a race condition because you can't guarantee that tempBool will be set correctly before your flipImageAndTextForView:... method returns. transitionWithView:duration:options:animations:completion: does not block. In any case the following lines:
if(tempBool)
{
return TRUE;
}
else{
return FALSE;
}
should have just been replaced with:
return tempBool;
If it works the way you wrote it and not the second way, that is just another symptom of the inherent race condition here.
The best thing to do is to not structure your code like this at all. Either test imageFlipDisplayed outside the animation block altogether or else use the completion block of transitionWithView:duration:options:animations:completion: to send a message to somewhere else with the information you need when the animation is complete.
The problem is that the value is only set when the animation starts, which is probably the next iteration of the runLoop. You could make the same text before the UIView animation call, which seems like a reasonable thing to do, or you could have the completion block message back to something.

Resources