I've looked high and low for a solutions for this and I've gotten nowhere.
I want to develop an iOS app that is pretty much a simple tap, animate and play sound.
So I'd have a map of say a farm and when I click the sheep, it will animate and make some noise. I have managed to achieve this and its all good.
My issue and question is being able to click an already animating button.
So when the view loads i would like to have cloud buttons move from left to right in the sky slowly, again I have already achieved this using this code
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidLoad];
[UIView animateWithDuration:8.5
delay:1.0
options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat | UIViewAnimationOptionCurveEaseInOut
animations:^{
cloudAnimate.center = CGPointMake(-100.0, 100.0);
}
completion:NULL];
}
BUT, when the buttons are animating from left to right and back they are not clickable so the touch I want for spinning the clouds and playing the sound doesn't work. I have read a few other posts that say that this can not be done but I have a load of apps on my iPhone and iPad that do this so anyone have any idea how this can be achieved?
Any help would be greatly appreciated as I'm pulling my hair out.
Thanks in advance.
EDIT:
OK so thanks to a combination of answers below I have almost got this to work
So I am using this to set the initial CGPoint:
- (void)viewDidLoad
{
[super viewDidLoad];
// Move UIView
cloudAnimate.center = CGPointMake(400.0, 100.0);
}
And to animate the cloud from left to right I am using the same code as i originally had with a slight tweak:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidLoad];
[UIView animateWithDuration:8.5
delay:1.0
options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat | UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction
animations:^{
cloudAnimate.center = CGPointMake(100.0, 100.0);
}
completion:NULL];
}
And now instead of IBAction I am using this:
-(void) touchesBegan:(NSSet*) touches withEvent:(UIEvent *) event {
CGPoint point = [[touches anyObject] locationInView:self.view];
if([self.cloudAnimate.layer.presentationLayer hitTest:point]) {
SystemSoundID soundID;
NSString *soundFile = [[NSBundle mainBundle] pathForResource:#"Super_Mario_Bros_Mushroom" ofType:#"mp3"];
AudioServicesCreateSystemSoundID((__bridge CFURLRef) [NSURL fileURLWithPath:soundFile], & soundID);
AudioServicesPlaySystemSound(soundID);
cloudAnimate.imageView.animationImages = [NSArray arrayWithObjects:[UIImage imageNamed:#"panda-png-chewing"],[UIImage imageNamed:#"panda-png-happy"],nil];
cloudAnimate.imageView.animationDuration = 0.2;
cloudAnimate.imageView.animationRepeatCount = 6;
[cloudAnimate.imageView startAnimating];
}
}
So now it does almost exactly what I want but if you hit the cloud when it is at its finishing CGPoint (even though it will carry on moving) the app crashes. If I hit it while it is moving it makes a sound and animate.
Anyones have any suggestions as to why this is?
When you animate a view you set the view in its final state. This way you can only detect touches in the final position where you are animating.
However, it is possible to go around that issue. You need to catch the touch event and comparate with the presentationLayer.
-(void) touchesBegan:(NSSet*) touches withEvent:(UIEvent *) event {
CGPoint point = [[touches anyObject] locationInView:self.view];
if([self.cloudAnimate.layer.presentationLayer hitTest:point]) {
//do something
}
}
The presentation layer has information about the visual position of the CALayer that is associated with your cloudAnimate.
You may want to use UIViewAnimationOptionAllowUserInteraction as well.
The short answer is that you can't tap views while they are animating. The reason is that the views don't actually travel from the start to the end location. Instead, the system uses a "presentation layer" in Core Animation to create the appearance of your view object moving/rotating/scaling/whatever.
What you have to do is attach a tap gesture recognizer to a containing view that completely encloses the animation (maybe the entire screen) and then write code that looks at the coordinates of the tap, does coordinate conversion, and decides if the tap is on a view that you care about. If the tap is on a button and you want the button to highlight you'll need to handle that logic too.
I have a sample project on github that shows how to do this when you are doing both UIView animation and Core Animation with CABasicAnimation (which animates layers, not views.)
Core Animation demo with hit testing
Related
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!
I found some questions and answers here on stackoverflow for that problem, but none of the solutions there solved my problem.
My iOS App has the ability to play some music with a nice music player. I designed it with Xcode's Interface Builder and dragged out a UIView and changed its class to MPVolumeView. Everything works fine when I'm debugging my app on my iPhone 6.
Here is my problem: I also dragged out a UITapGestureRecognizer on my whole view which contains my controls like
play/pause, next/previous track (...)
and also my MPVolumeView. When I tap on that view it should fade out and disappear. Then I added a UITapGestureRecognizer on my UIImageView which shows my artwork image of the song. When I tap this image view, it should fade in my view with all controls in int - that's working properly.
BUT: When I slide the knob of the volume slider just a little bit, or if I am just touching it, the view still disappears. It seems like my MPVolumeView is forwarding my touch or something like that. I tried setting userInteractionEnabled = false on my volume slider, but that didn't help. I also set the delegate of my gesture recognizer to self and added the
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
NSLog(#"tapped");
if([gestureRecognizer.view isMemberOfClass:[UIImageView class]]) {
return true;
}
return false;
}
function to my code, which returns true or false, depending on which view I'm tapping. When I'm accessing the gestureRecognizer.view property, it doesn't recognize my MPVolumeView, just the UIView in the background.
Here my two methods which are fired after when the TapGestureRecognizers are fired:
- (IBAction)overlayViewTapped:(UITapGestureRecognizer *)sender {
if(sender.state == UIGestureRecognizerStateEnded) {
[UIView animateWithDuration:0.3
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{ self.blackOverlayView.alpha = 0.0; self.normalTimeLabel.alpha = 1.0; }
completion:nil];
}
}
- (IBAction)imageViewTapped:(UITapGestureRecognizer *)sender {
[UIView animateWithDuration:0.3
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{ self.blackOverlayView.alpha = 1.0; self.normalTimeLabel.alpha = 0.0; }
completion:nil];
}
Please help me, I'm nearly going nuts with that ..
EDIT: My music player looks like this:
After I tap anywhere on the view (except the subviews), the view should fade out and hide everything, just show the artwork image of the song and the current elapsed time. This will look like this:
As I said - the problem is, if I just tap the volume slider or slide it just a little bit, my UITapGestureRecognizer fires and fades out my complete view. How can I prevent that?
It is behaving the way it is simply because you added the gesture recognizer to the entire UIView, which includes the volume slider and whatnot.
Instead of detecting the touch in the entire view, check to see if the touch is in the area you want it.
Create a CGRect property, I'll call it touchArea:
#property CGRect touchArea;
Then specify the size of the touchArea (you can do this in the viewDidLoad):
touchArea = CGRectMake(0.0, 240.0, 320.0, 240.0);
You will have to find out where you want this and how big it should be and replace my example values with the real ones. A simple way of cheating this is to take something like a UILabel in IB and positioning and sizing it to your desire, then go to the size inspector pane and get the x, y, width and height values.
Then, before you do your fade animation, check to see if the touch was in the touchArea:
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer
{
CGPoint touchPoint = [gestureRecognizer locationInView:self.view];
if (CGRectContainsPoint(touchArea, touchPoint))
{
//do your animation here.
}
}
As a note, I would set a BOOL to check whether or not the view is faded in or out, so you can always check before animating.
I'm trying to make an image go full screen on tap, which doesn't sound so complicated.
This it currently doing the work after the tap is detected:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInView:self.view];
if (self.contentView.frame.origin.y >= 0 && CGRectContainsPoint(self.bottleImageView.frame, touchLocation)) {
[UIView animateWithDuration:0.5 delay:0 options:0 animations:^{
[bottleImageView setFrame:[[UIScreen mainScreen] bounds]];
bottleImageView.backgroundColor = [UIColor blackColor];
}completion:^(BOOL finished){}];
...
}
There's a problem though. I need to bring the UIImageView to the front, so that it draws the image in front of the other views. When I add in [self.view bringSubviewToFront:bottleImageView]; to the mix (either before the animation block, in the completion block, or after this code), things change. On the first tap, the UIImageView background changes to black, and on the second tap the view expands to full screen.
I'm sure it's just something small and dumb that I've overlooked.
EDIT: I've added the touch code
EDIT (2): I may have missed some vital information. I thought I'd try auto layout for this project. I'm guessing there's something there that's not letting everything work as intended?
You can use MHFacebookImageViewer
for this purpose. It is very simple to use.
Perhaps you have the wrong view content mode, add this line to where after the image is being set:
bottleImageView.contentMode = UIViewContentModeScaleAspectFit;
OK. The code above is fine for expanding a view.
I found there was a problem with the Autolayout restraints. I couldn't tell you exactly what I changed that fixed the problem - but a few hours debugging restraints solved the issue.
I found a much better solution however - Facebook POP works much better, and looks a lot slicker.
Add
[self.view bringSubviewToFront:bottleImageView]
immediately after you add
[self.view addSubview:bottleImageView]
What I wish to do is to fade-in and fade-out some custom annotation views. In order to do this I need to have the pointers to those views. Have been thinking of collecting these pointers in mapView:viewForAnnotation. But I am not sure if this is the right way.
The reason is since annotation views are set up to be recycled, I worry that they can be detached from their annotations once they are moved out of the screen. So my thinking is, view pointers collected in mapView:viewForAnnotation, may not be reliable since we don't know if they are still on the screen or not.
Hope somebody can suggest a proper way to keep track of these view pointers. Or suggest some other standard or reliable way of fading in and out the annotation views.
Update :
Just found out that viewForAnnotation does work. And I don't really need to keep track of the annotations (at least in this case) if they are on screen or not.
Try this where you get to the zoom that you want to animate the annotationViews:
for (MyAnnotation *annotation in myMapView.annotations)
{
MKAnnotationView *annotationView = [myMapView viewForAnnotation:annotation];
//Do your animation to the annotationView
//Example for fadeOut - fadeIn
[UIView animateWithDuration:0.5 animations:^{
annotationView.alpha = 0.2;
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.5 animations:^{
annotationView.alpha = 1;
}];
}];
}
If you have all kinds of annotationView types and you want to animate just part of them, then you will need to check the type before the animation.
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;
}