I have a tap gesture on my view that I only want to be able to fire after a certain point and after a different function has run. My solution was to create a outlet for the tap gesture and then call the Enabled function setting to TRUE or FALSE as needed. The code looks like this, with the second function being the tap gestures
- (IBAction)butt:(id)sender
{
if(game == FALSE)
{
tapges.enabled = FALSE;
int sec;
sec = arc4random() % 5;
if(sec == 0){sec++;}
sleep(sec);
tapges.enabled = TRUE;
game = TRUE;
gun.hidden = FALSE;
start = [[NSDate date] timeIntervalSince1970];
}
}
- (IBAction)tap:(id)sender
{
if(game == TRUE)
{
end = [[NSDate date] timeIntervalSince1970];
double total = end - start;
NSString *myString = [NSString stringWithFormat:#"%lf", total];
ss.text = myString;
game = FALSE;
gun.hidden = TRUE;
tapges.enabled = FALSE;
}
}
However it doesn't seem to work. Even after the first function has set tapges.enabled = FALSE if you tap before it reaches tapges.enabled = TRUE it still just queues the tap function for running after the butt function runs.
There is a way to prevent a UIGestureRecognizer from firing (or to call another function before it fires) through using UIGestureRecognizerDelegate method gestureRecognizerShouldBegin but I think the issue is more a question of logic.
Not sure I entirely understand your task but wouldn't something more simple like:
- (IBAction)butt:(id)sender
{
if(game == FALSE)
{
[self callOtherMethod];
}
}
Or
- (IBAction)butt:(id)sender
{
if(game == FALSE)
{
//do set up
} else {
// do other setup
}
}
Related
I'm trying to indefinitely count up a number of taps on a UILabel, every time the label is tapped a different string is displayed. However, it stops at 2 taps always using ++ or += 1
-(void)cycleLabelString {
int taps;
taps += 1;
NSLog(#"taps = %d", taps);
if (taps == 1) {
self.randomLabel.text = [NSString stringWithFormat:#"$%.2f", pagesCount * 0.69];
} else if (taps == 2) {
self.randomLabel.text = [NSString stringWithFormat:#"%d", pagesCount];
} else if (taps >= 3) {
NSLog(#" >= 3");
}
}
int taps;
This initializes a new taps each time, and it is initialized to zero by default. You probably want it in a property. Make a private class extension at the top of your .m file like this:
#interface YourClassNameHere ()
#property (nonatomic) int taps;
#end
And then to use it:
-(void)cycleLabelString {
self.taps += 1;
NSLog(#"taps = %d", self.taps);
if (self.taps == 1) {
self.randomLabel.text = [NSString stringWithFormat:#"$%.2f", pagesCount * 0.69];
} else if (self.taps == 2) {
self.randomLabel.text = [NSString stringWithFormat:#"%d", pagesCount];
} else if (self.taps >= 3) {
NSLog(#" >= 3");
}
}
is this function getting called everytime the label is tapped? if so, you will need to define taps as a global variable, as it is being reset everytime the label is tapped. try something like:
int taps;
-(void)cycleLabelString {
...
I am working on an iOS 7+ app and would like to animated change the content of an UILabel. I do not want do do any graphical animation like fade out old content / fade in new content. Thus all the standard animation features iOS offers like Layer animations or animation blocks cannot be uses (at least I think so).
Assume the UILabel shows some meter values like "200 V" and this text should be changed to "400 V". The text should not just jump from "200 V" to "400 V" but should be counted up using some easing function: "200 V", "220 V", "240 V"... "390 V", "395 V" ... "400 V"
In Android could easily be solved using a ValueAnimator:
ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
animation.setInterpolation(new EaseOutInterpolator());
animation.setDuration(2500);
animation.setStartDelay(500);
animation.addUpdateListener(new AnimatorUpdateListener() {
#Override
public void onAnimationUpate(ValueAnimator animator) {
float currentValue = animator.getAnimatedValue.floatValue();
label1.setText(String.format("%.2", fromValue1 + ((toValue1 - fromValue1) * currentValue)));
label2.setText(String.format("%.2", fromValue2 + ((toValue2 - fromValue2) * currentValue)));
...
}
});
animation.start();
Is there such thing in iOS as well? I found different solution for this but they are all pretty old (2010/11) and all end up implementing this behavior manually using NSTimer and own easing functions.
It is out of question that one can implement this on his own, but this would be quite cumbersome and not very elegant. So: Is there something build in iOS to solve this or are there at least convenient third party implementation available?
Thank you very much!
Since I found no tailored solution for this I created my own: A simple Animator Class which handles the Easing:
// MyValueAnimation.h
typedef void (^MyAnimationBlock)(double animationValue);
#interface MyValueAnimation : NSObject
- (void)startAnimation:(MyAnimationBlock)animationBlock runtime:(NSUInteger)runtime delay:(NSUInteger)delay;
#end
// MyValueAnimation.m
#import "MyValueAnimation.h"
// Number of seconds between each animation step
#define kStepSize 0.05
#interface MyValueAnimation () {
NSTimer *timer;
NSUInteger totalRunTime; // Total duration of the animation (delay not included)
NSUInteger currentRuntime; // Time the animation is already running
MyAnimationBlock animationBlock;
}
#end
#implementation MyValueAnimation
- (void)startAnimation:(MyAnimationBlock)block runtime:(NSUInteger)runtime delay:(NSUInteger)delay {
if (timer != nil)
[timer invalidate];
timer = nil;
totalRunTime = runtime;
animationBlock = block;
currentRuntime = 0;
if (block != nil) {
if (delay > 0) {
// Wait to delay the start. Convert delay from millis to seconds
double delaySeconds = (double)delay / 1000.0;
timer = [NSTimer scheduledTimerWithTimeInterval:delaySeconds target:self selector:#selector(delayTick:) userInfo:nil repeats:false];
} else {
// Run the animation
timer = [NSTimer scheduledTimerWithTimeInterval:kStepSize target:self selector:#selector(animationTick:) userInfo:nil repeats:true];
}
}
}
- (void)delayTick:(NSTimer *)delayTimer {
// End of delay -> run animation
[delayTimer invalidate];
timer = [NSTimer scheduledTimerWithTimeInterval:kStepSize target:self selector:#selector(animationTick:) userInfo:nil repeats:true];
}
- (void)animationTick:(NSTimer *)animationTimer {
NSUInteger step = 1000 * kStepSize; // step size/length in milli seconds
currentRuntime += step;
double progress = MIN((double)currentRuntime / (double)totalRunTime, 1.0);
// Progress is a value between 0 and 1. The easing function maps this
// to the animationValue which is than used inside the animationBlock
// to calculate the current value of the animiation
double animationValue = [self customEaseOut:progress];
if (animationBlock != nil)
animationBlock(animationValue);
if (progress >= 1.0) {
// Animation complete
[timer invalidate];
timer = nil;
}
}
- (double)customEaseOut:(double)t {
// Use any easing function you like to animate your values...
// http://rechneronline.de/function-graphs/
// http://sol.gfxile.net/interpolation/
return (1 - pow(1-t, 2));
}
#end
// =============================================================
// Some code using the animation
- (void)animateValueFrom:(double)fromValue to:(double)toValue {
if (valueAnimation == nil)
valueAnimation = [[MyValueAnimation alloc] init];
MyAnimationBlock animationBlock = ^(double animationValue) {
double currentValue = fromValue + ((toValue - fromValue) * animationValue);
someLabel.text = [NSString stringWithFormat:"%dV", currentValue];
};
[valueAnimation startAnimation:animationBlock runtime:1500 delay:500];
}
Maybe not the prettiest solution but it works :-)
I know a solution but its for fast iteration because final iterations can jump through value (looks not beautifully if there is a slow search), but the decision simple and short, can not the most beautiful from a architecture(but it can be corrected if it is necessary) realisation.
- (void)someAction
{
[self animateValue:0 toValue:1111 withStep:7 andIntervalSpeed:5];
}
-(void)animateValue:(int)value toValue:(int)toValue withStep:(int)step andIntervalSpeed:(int64_t)intervalSpeed
{
self.currentValue = value; // #property (nonatomic) int currentValue;
NSUInteger numberofIteration = (toValue - value)/step;
int64_t interval = 0.0;
for (NSUInteger i = 0; i < numberofIteration; i++) {
dispatch_time_t start = DISPATCH_TIME_NOW;
interval += intervalSpeed;
dispatch_after(dispatch_time(start, interval * USEC_PER_SEC), dispatch_get_main_queue(), ^{
if (((toValue - value)%step != 0) && (i == (numberofIteration-1)))
{
self.currentValue = toValue;
}
else
{
self.currentValue+= step;
}
NSLog(#"%d",self.currentValue);
self.someLabel.text = [NSString stringWithFormat:#"%d",self.currentValue];
});
}
}
The below code for the most part works when selecting items, the bar button enables, but as soon as I unselect 1 with say 3 still selected, it disables.
How can I use the below code to disable when the count reaches 0 items selected?
- (void)assetsTableViewCell:(WSAssetsTableViewCell *)cell didSelectAsset:(BOOL)selected atColumn:(NSUInteger)column
{
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
// Calculate the index of the corresponding asset.
NSUInteger assetIndex = indexPath.row * self.assetsPerRow + column;
WSAssetWrapper *assetWrapper = [self.fetchedAssets objectAtIndex:assetIndex];
assetWrapper.selected = selected;
// Update the state object's selectedAssets.
[self.assetPickerState changeSelectionState:selected forAsset:assetWrapper.asset];
// Update navigation bar with selected count and limit variables
dispatch_async(dispatch_get_main_queue(), ^{
if (self.assetPickerState.selectionLimit) {
self.navigationItem.title = [NSString stringWithFormat:#"%# (%lu/%ld)", [self.assetsGroup valueForProperty:ALAssetsGroupPropertyName], (unsigned long)self.assetPickerState.selectedCount, (long)self.assetPickerState.selectionLimit];
}
});
}
Below is what needs adjusting.
if (selected == 1) {
self.navigationItem.rightBarButtonItem.enabled = YES;
} else if (selected == 0) {
self.navigationItem.rightBarButtonItem.enabled = NO;
}
selected will give you the state of the current asset so it wouldn't be wise to check this.
We need to check for some kinda global thing; basically to check for previous selections.
Looking at your navigationItem.title, it seems assetPickerState.selectedCount should do the trick.
So... maybe this?? (not sure but anyways...)
if (self.assetPickerState.selectedCount == 0) {
self.navigationItem.rightBarButtonItem.enabled = NO;
}
else {
self.navigationItem.rightBarButtonItem.enabled = YES;
}
Looks like you're only explicitly enabling the button when you have 1 selected (not more than that). Just going out on a whim but this might work (only disable if zero, otherwise enable it).
if (selected == 0) {
self.navigationItem.rightBarButtonItem.enabled = NO;
} else {
self.navigationItem.rightBarButtonItem.enabled = YES;
}
I have a collision handler detecting for a game over situation, however when the rocket lands (else if), the labels become visible no problems, but there is a second where the touch down even still registers, then the rocket takes off again, then the touch disables. Is there any obvious thing I'm doing wrong?
-(BOOL)ccPhysicsCollisionPreSolve:(CCPhysicsCollisionPair *)pair rocket:(CCNode *)nodeA landingPad:(CCNode *)nodeB{
//if travelling too fast on landing..
if (_rocket.physicsBody.velocity.y < maximumVerticalVelocity){
_crashNotice.visible = TRUE;
crashed = TRUE;
[_rocket removeFromParentAndCleanup:YES];
self.userInteractionEnabled = FALSE;
}else if (_rocket.physicsBody.velocity.y > 0.01){
self.userInteractionEnabled = FALSE;
//show game won state
_scoreLabel.visible = FALSE;
_showScoreLabel.visible = TRUE;
}return TRUE;
}
Instead of enable and disable touch, handle through bool value.
In class.h file declare this
bool mAllowTouch;
In Init/onEnter :
-(void)onEnter
{
[super onEnter];
mAllowTouch = true;
self.userInteractionEnabled = true;
}
In your function
-(BOOL)ccPhysicsCollisionPreSolve
{
if(YOUR_CONDITION_TO_STOP_TOUCH)
{
mAllowTouch = false;
}
else
{
mAllowTouch = true;
}
}
In touch function, use mAllowTouch to process your touch.
-(void) touchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
if(mAllowTouch)
{
//handle touch here.
}
}
I am making a stopwatch, but I only have the start button working. When the start button is pressed it enters a loop:
- (void)stopwatch
{
NSInteger hourInt = [hourLabel.text intValue];
NSInteger minuteInt = [minuteLabel.text intValue];
NSInteger secondInt = [secondLabel.text intValue];
if (secondInt == 59) {
secondInt = 0;
if (minuteInt == 59) {
minuteInt = 0;
if (hourInt == 23) {
hourInt = 0;
} else {
hourInt += 1;
}
} else {
minuteInt += 1;
}
} else {
secondInt += 1;
}
NSString *hourString = [NSString stringWithFormat:#"%02d", hourInt];
NSString *minuteString = [NSString stringWithFormat:#"%02d", minuteInt];
NSString *secondString = [NSString stringWithFormat:#"%02d", secondInt];
hourLabel.text = hourString;
minuteLabel.text = minuteString;
secondLabel.text = secondString;
CGRect hourFrame = self->hourBar.frame;
CGRect minuteFrame = self->minuteBar.frame;
CGRect secondFrame = self->secondBar.frame;
if ((NSInteger)hourFrame.size.height != hourInt) { // check if we need to modify
hourFrame.origin.y -= ((hourInt * 10.0) - hourFrame.size.height);
hourFrame.size.height = (hourInt * 10.0);
self->hourBar.frame = hourFrame;
}
if ((NSInteger)minuteFrame.size.height != minuteInt) { // check if we need to modify
minuteFrame.origin.y -= ((minuteInt * 4.0) - minuteFrame.size.height);
minuteFrame.size.height = (minuteInt * 4.0);
self->minuteBar.frame = minuteFrame;
}
if ((NSInteger)secondFrame.size.height != secondInt) { // check if we need to modify
secondFrame.origin.y -= ((secondInt * 4.0) - secondFrame.size.height);
secondFrame.size.height = (secondInt * 4.0);
self->secondBar.frame = secondFrame;
}
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(stopwatch) userInfo:nil repeats:NO];
}
When the stop button is pressed, I want this loop to be paused so that when the user presses start, it resumes the stopwatch.
When the reset button is pressed, I want the loop to stop and reset back to 0.
If you could make your answer as simple as possible, that would be really great because I'm only a beginner!
You should take the timer out of your 'loop' method and make it repeating. It should be the thing that is driving the stopwatch, not that fact that you have started your 'loop'. Then, you can stop the timer when you need to and restart it later. You can also google to find out the correct way to pause the timer (you need to change the timer to start at a specified fire date to be 100% accurate, but just knowing how many seconds are left may be enough for your case).
Based on your previous question it sounds like you have moved your timer outside of your stopwatch method. Remember, timers and loops are two very different things.
You can stop an NSTimer by calling invalidate on it. Please read Apple's documentation for more details.
One way that might not be completely thread safe but I just tested it and it works is to have a variable that is set to true when you click start and false when you click stop.
For example, in my .h, I added #property (nonatomic) BOOL go;, then as the first line of the method you provided, add an if check that looks like this:
- (void)stopwatch {
if (!self.go) return;
//..do the rest of your stopwatch code
//the timer call
}
then my start and stop button calls look like:
- (void)start {
if (!self.go) { // Do not call the stopwatch method again if it is already going.
self.go = YES;
[self stopwatch];
}
}
- (void)stop {
self.go = NO;
}
Loop is the wrong construct for this type of operation. For better control, use NSTimer.
A relevant example can be found here.
EDIT:
My above answer was based on prior version of your question.
So yes, the NSTimer should be the controlling thing for your loop, not part of the loop.
General implementation:
Start should only mark timer starting. Also note the current time from system clock. (this is optional though)
Timer function should change value of the time variable, and compare it against the stopwatch set value. If time elapsed == set value, stop the timer using invalidate.
Stop should interrupt the timer and stop it, and also the elapsed time value should be set to 0. Time Variable should be reset too, if you do not want to reuse this same time value again.