I am having difficulty getting a UITapGestureRecognizer to detect every tap that is occurring. For context, I am making a drumming app. I have gotten the tap gesture recognizer to work correctly, but the user should be able to tap very quickly to drum, and the recognizer is only picking up on let's say a tap every so often (if you try to tap as quickly as you can).
I am guessing this has to do with iOS categorizing very quick taps in a row as one gesture, but is there any way to get every time a finger goes down as a tap event?
I have tried using UILongPressGestureRecognizer with a small press duration.
I have tried setting the requiredNumberofTouches and requiredNumberofTaps to 1.
The code does not seem necessary to post in order to answer the question, but I will post it if you request it.
Any help would be most appreciated!
EDIT: I am adding my code, since it may help:
-(void)tap:(UITapGestureRecognizer *)tapGestureRecognizer {
CGPoint location = [tapGestureRecognizer locationInView:[self view]];
switch ([self validateTouchLocation:location]) {
case 0:
return;
case 1:
[[ASSoundManager sharedManager] playSoundNamed:#"SD0025" ofType:#"mp3"];
break;
case 2:
default:
[[ASSoundManager sharedManager] playSoundNamed:#"SD0010" ofType:#"mp3"];
break;
}
float tapWidth = 30;
ASDrumTapView *drumTapView = [[ASDrumTapView alloc] initWithFrame:CGRectMake(location.x - (tapWidth / 2), location.y - (tapWidth / 2), tapWidth, tapWidth)];
[drumTapView setBackgroundColor:[UIColor clearColor]];
[drumTapView setNeedsDisplay];
[[self view] addSubview:drumTapView];
float animDuration = 0.75;
CGRect frame = [drumTapView frame];
[UIView animateKeyframesWithDuration:animDuration
delay:0.0
options:0
animations:^{
[UIView addKeyframeWithRelativeStartTime:0
relativeDuration:animDuration
animations:^{
[drumTapView setFrame:CGRectInset(frame, -frame.size.width, -frame.size.height)];
}];
[UIView addKeyframeWithRelativeStartTime:0
relativeDuration:3*animDuration/5
animations:^{
[[drumTapView layer] setOpacity:0.0];
}];
}
completion:^(BOOL finished) {
[drumTapView removeFromSuperview];
}];
}
There could be many reason:
One possible reason could be fact that your audio engine cannot play next sound before previous sound has finished.
Check multipleTouchEnabled property on UIView
If your view is embedded in UIScrollView or any derived class, check delaysContentTouches property
While I appreciate everyone's input, I figured it out on my own.
In my code, I am running a keyframe animation with options set to 0. What this means is that during the animation, user interaction with the view is not enabled.
By setting options to UIKeyframeAnimationOptionAllowUserInteraction, I am able to pick up on taps even though one of the subviews is still in animation.
Hope this helps someone else!
Related
I'm writing a little card game where 4 cards are dealt to the screen and the user can tap each card to reveal (and again to hide) it.
Each card-front and card-back are stored in image views. A UIButton catches the user tap and should flip the card over.
I've added front and back of the card as subviews to a container view and I use the method UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight for the animation.
Please see the code below, I've removed the handling of the 4 different cards in the methods below for the sake of simplification & readability. Also I've replaced some array/filename-juggling for the cards with static image-names for front and back here.
The strange thing with this code now is the sometimes it works as expected 10 times in a row (i.e. the flipping animation is shown) but sometimes no animation is shown at all (i.e. the other card side is shown but without the flipping.). Then it's the other way round: Sometimes the card is shown without any animation for 7 or 8 times then suddenly the flipping-animation is shown.
It's driving me nuts as I can't see the reason for this strange behaviour.
Do you have any idea? I'm building for iOS 8 to iOS 10.
From the .h file:
#interface GameViewController : UIViewController
{
UIImageView *cardback1;
UIImageView *cardfront1;
UIView *containerView;
BOOL c1Flipped;
// much more...
}
And from the .m file:
-(void)flipCardButtonClicked:(id)sender
{
containerView = [[UIView alloc] initWithFrame: CGRectMake(25,420,220,300)];
[self.view addSubview:containerView];
c1Flipped = !c1Flipped;
cardback1 = [[UIImageView alloc] initWithFrame: CGRectMake(0,0,220,300)];
cardfront1 = [[UIImageView alloc] initWithFrame: CGRectMake(0,0,220,300)];
if (c1Flipped)
{
cardback1.image = [UIImage imageNamed:#"backside.png"];
cardfront1.image = [UIImage imageNamed:#"frontside.png"];
}
else
{
cardback1.image = [UIImage imageNamed:#"frontside.png"];
cardfront1.image = [UIImage imageNamed:#"backside.png"];
}
[containerView addSubview:cardfront1];
[containerView addSubview:cardback1];
[cardfront1 release];
[cardback1 release];
[self performSelector:#selector(flipSingleCard) withObject:nil afterDelay:0.0];
}
-(void)flipSingleCard
{
[containerView.layer removeAllAnimations];
[UIView beginAnimations:#"cardFlipping" context:self];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(flipDidStop:finished:context:)];
[UIView setAnimationDuration:0.5];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:containerView cache:YES];
[containerView exchangeSubviewAtIndex:0 withSubviewAtIndex:1];
[UIView commitAnimations];
}
-(void)flipDidStop:(NSString*)animationID finished:(BOOL)finished context:(void *)context
{
[containerView removeFromSuperview];
[containerView release];
}
My guess is that [self performSelector:#selector(flipSingleCard) withObject:nil afterDelay:0.0]; is the culprit. Seems like this could be a timing issue. Have you tried adding an actual delay to this? Say...0.1? I believe that doing this could fix this, OR just simply calling the method directly instead of using performSelector.
Looks like it indeed was a timing issue as suspected by BHendricks.
Adding a 0.1 delay solved the problem.
Thanks a lot.
I created some animations in my project. Basically, I use UIView animate and CGAffineTransform, but a very strange thing happened and I have no idea. Hope someone can help me solve this problem. Thanks in advance.
This is the strange thing:
After the user clicks on a button, the button slides off screen and another two buttons slide on the screen (I just changed the center point of these buttons to achieve this animation). And, some time later, a view on the screen start shaking (I use CGAffineTransform to achieve this).
At this moment, the strange thing happens - the button that previous slid off screen show up at its original position again and the other two buttons disappear (No animation, just shows up and disappear).
The following is the related code,
1) Button slide off and slide in animation related code
- (IBAction)start:(id)sender
{
// 1. Slide in cancel and pause button
[UIView animateWithDuration:0.5f animations:^{
[startButton setCenter:CGPointMake(startButton.center.x + 300.0f, startButton.center.y)];
[cancelButton setCenter:CGPointMake(cancelButton.center.x + 300.0f, cancelButton.center.y)];
[pauseButton setCenter:CGPointMake(pauseButton.center.x + 300.0f, pauseButton.center.y)];
} completion:^(BOOL finished) {
if (finished) {
NSLog(#"Move finished");
}
}];
}
2) The view shaking animation related code
- (void)shakeView:(UIView *)viewToShake
{
CGFloat t = 2.0;
CGAffineTransform translateRight = CGAffineTransformTranslate(CGAffineTransformIdentity, t, 0.0);
CGAffineTransform translateLeft = CGAffineTransformTranslate(CGAffineTransformIdentity, -t, 0.0);
viewToShake.transform = translateLeft;
[UIView animateWithDuration:0.07 delay:0.0 options:UIViewAnimationOptionAutoreverse|UIViewAnimationOptionRepeat animations:^{
[UIView setAnimationRepeatCount:2.0];
viewToShake.transform = translateRight;
} completion:^(BOOL finished) {
if (finished) {
[UIView animateWithDuration:0.05 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
viewToShake.transform = CGAffineTransformIdentity;
} completion:nil];
}
}];
}
This isn't weird, it's a common problem with
auto layout. If you move UI elements by changing frames, then as soon as something else takes place that requires laying out views, the moved views will revert to the position defined by their constraints. To fix it, you either need to turn off auto layout, or do your animations by changing constraints not frames.
I have tried your code in my test project. The problem is probably because you are using Autolayout in your xib.
Please checking your xib file and uncheck the Autolayout property.
I have a slider that when it begins to change the values, another label shows up (lets the user see what they are doing). The only problem is that I need to know when the user has finished editing the slider so I can make that UILabel go away again. Is there a way to do this? The code below shows what I do when the sliders value begins to change. Thank you for you help!
- (IBAction)sliderValueChanged:(UISlider *)sender {
tipPercentLabel.text = [NSString stringWithFormat:#"%.f", ([sender value] * 100)];
tipPercentLabel2.text = [NSString stringWithFormat:#"%.f", ([sender value] * 100)];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:1.0];
[tipPercentLabel setAlpha:0.3];
[tipPercentLabel2 setAlpha:1.0];
[UIView commitAnimations];
[self performSelector:#selector(autoTipCalc) withObject:nil afterDelay:0.01];
}
You have already added one target/action pair for UIControlEventValueChanged, all you need to do is call addTarget:action:forControlEvents: again with a different selector and a control event of UIControlEventTouchUpInside (or any other that you're interested in).
You can add target to your slider as if you would to UIButon handling UIControlEventTouchUpInside (do it also for outside). Optionally or alternatively you could perform a selector after delay each time the value changes to check when the slider value has last changed and remove the label if it has taken long enough.
I want something similar in purpose to Flipboard slight flipping animation on app start. Flipboard when launched has this slight flipping of up and down to show users unfamiliar with the interface that it is flippable.
I have a UIScrollView I want to animate a bit to show the user that it's scrollable. So I want to scroll to the right a little bit and back. UIScrollView has a setContentOffset:animated: message without a completion clause. I find that calling it twice results in seemingly no animation. What if I want an animation after animation in succession?
EDIT:
Thanks Levi for the answer.
And for the record, there is UIViewAnimationOptionAutoreverse and UIViewAnimationOptionRepeat that I can use. So this is what I ended up with that works.
CGPoint offset = self.scrollView.contentOffset;
CGPoint newOffset = CGPointMake(offset.x+100, offset.y);
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionAutoreverse |UIViewAnimationOptionRepeat animations:^{
[UIView setAnimationRepeatCount: 2];
[self.scrollView setContentOffset:newOffset animated: NO];
} completion:^(BOOL finished) {
[self.scrollView setContentOffset:offset animated:NO];
}];
For a scrollView, tableView or collectionView if you do something like this:
[self.collectionView setContentOffset:CGPointMake(self.collectionView.contentOffset.x+260.0,
self.collectionView.contentOffset.y)
animated:YES];
then you'll get back a:
-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
when the scroll finishes.
You do NOT get this callback if the user moves the view.
Two options:
1) Use the -(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView delegate callback
2) Try to put it into an animation block (with ... animated:NO];), which has the completion part.
I have a problem with a UIView-subclass object that I am rotating using Core Animation in response to a UISwipeGesture.
To describe the context: I have a round dial that I have drawn in CG and added to the main view as a subview.
In response to swipe gestures I am instructing it to rotate 15 degrees in either direction dependent on whether it;s a left or right swipe.
The problem that it will only rotate each way once. Subsequent gestures are recognised (evident from other actions that are triggered) but the animation does not repeat. I can go left once then right once. But trying to go in either direction multiple times doesn't work. Here's the relevant code, let me know your thoughts...
- (IBAction)handleLeftSwipe:(UISwipeGestureRecognizer *)sender
{
if ([control1 pointInside:[sender locationInView:control1] withEvent:nil])
{
//updates the display value
testDisplay.displayValue = testDisplay.displayValue + 0.1;
[testDisplay setNeedsDisplay];
//rotates the dial
[UIView animateWithDuration:0.25 animations:^{
CGAffineTransform xform = CGAffineTransformMakeRotation(radians(+15));
control1.transform = xform;
[control1 setNeedsDisplay];
}];
}
CGAffineTransform xform = CGAffineTransformMakeRotation(radians(+15));
Do you keep a total of how far the rotation is. CGAffineTransformMakeRotation are not additive. Only the most recent is used. So you are setting it to 15 each time, not 15 more each time.
Here's a super simple example of rotating a view cumulatively. This rotates the view by 180 degrees each button press.
- (IBAction) onRotateMyView: (id) sender
{
[UIView animateWithDuration:0.3 animations:^{
myView.transform = CGAffineTransformMakeRotation(M_PI/2*rotationCounter);
} completion:^(BOOL finished){
//No nothing
}];
++rotationCounter;
}