iOS - How to Make an Animation Track Touches

iOS - How to make an animation track touches

What I don't grasp - and I'm comfortable using CAAnimations and gestures - is how something can be both animated and interactive.

It is because, having set up an animation, you can set that animation to any "frame" you wish. Thus you can track the animation in correspondence to the movement of a gesture.

The way this works is that an animation is a feature of the render tree, belonging to a CALayer. CALayer implements the CAMediaTiming protocol. The timeOffset of a CALayer thus determines what "frame" of an animation that layer displays. If a complex animation involves many different layers, no problem; just set the timeOffset of their mutual superlayer, to control the frame of the entire animation.

That in fact is exactly how the new iOS 7 interactive custom transition feature works (to which you rightly refer in your question). For instance, in this example code:

https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/iOS7bookExamples/bk2ch06p296customAnimation2/ch19p620customAnimation1/AppDelegate.m

... I keep updating the UIPercentDrivenInteractiveTransition to tell it how far through the gesture the user is, and the animation therefore tracks the gesture. Now ask yourself: how the heck is this possible???

Well, the UIPercentDrivenInteractiveTransition, in turn, behind the scenes, keeps adjusting the layer's timeOffset to portray the animation at the corresponding frame. (You can actually add logging code to my example to see that this is true.)

Moreover, when I end the gesture at an incomplete point, the animation either hurries to its end or runs backwards to its beginning - again, this is because of the CAMediaTiming protocol, which lets you change the speed of the animation, including a negative value to run it backwards.

iPhone animation based on input values (touches) not time

You may be able to use the hierarchical nature of timelines in layer trees to achieve what you’re looking for. Objects implementing CAMediaTiming, which include both CAAnimation and CALayer, inherit a timespace from their parent, which they can modify (via scaling, shifting and repeating) and propagate to their children. By setting the speed property of a layer to 0.0 and adjusting the timeOffset property manually, you can decouple that layer (and all of its sublayers) from the usual notion of time.

What you’d do in your case is define the animations for all your menu items to animate their position along your desired CGPath from time t0 to t1, with the appropriate timeOffset on each animation to keep your items appropriately spaced. Note that a beginTime of 0.0 on an animation is usually interpreted as starting from the time the animation was added to its layer, so if you want t0 to be 0.0, you'll probably have to set it to a tiny epsilon > 0.0.

CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
animation.beginTime = 1e-100;
animation.duration = 1.0;
animation.fillMode = kCAFillModeBoth;
animation.removedOnCompletion = NO;
animation.path = path;
animation.calculationMode = kCAAnimationPaced;
animation.timeOffset = timeOffset;

You’d then set the speed property to 0.0 on a parent layer (containing only these menu items) and update its timeOffset to values between t0 and t1 in response to your touch events.

This approach has two potential caveats. Because you’ve taken over the nature of time on this layer subtree, you probably won’t be able to animate other properties at the same time. Additionally, if you want coasting behavior for a fast flick, you’ll probably need to animate time forward on your own.

How to keep track of animation in Sprite Kit

As @KnightOfDragon suggested : animateWithTextures has a restore: parameter, if you set that to false, when you stop animating the last texture should stay on the sprite. If you go ahead and read the textures description, then you will know what texture it stopped on, and could plan accordingly

iOS Touch, Gestures, Animation

Why don't you consider using UIPanGestureRecognizer. You can use the translationInView: to move the box as moves the finger across. And when the gesture's state is UIGestureRecognizerStateEnded, you could use velocityInView: to get the desired follow up effect.

CollectionView messes with animations in touchesBegan and touchesEnded methods

Have you tried delaysContentTouches = false?

https://developer.apple.com/documentation/uikit/uiscrollview/1619398-delayscontenttouches

Tells the scrollView/collectionView to not delay touches on the cells. Worked for me to make responsive cells immediately when I started tapping on them. Didn't make my scrolling buggy either.

Tracking a Core Animation animation

I faced a similar problem. I used layers instead of views for the animation. You could try something like this.

  1. Draw each element as a CALayer and include them as sublayers for your container UIVIew's layer. UIViews are easier to animate, but you will have less control. Notice that for any view you can get it's layer with [view layer];
  2. Create a custom sublayer for your quadrilateral. This layer should have a property or several of properties you want to animate for this layer. Let's call this property "customprop". Because it is a custom layer, you want to redraw on each frame of the animation. For the properties you plan to animate, your custom layer class should return YES needsDisplayForKey:. That way you ensure -(void)drawInContext:(CGContextRef)theContext gets called on every frame.
  3. Put all animations (both circles and the quad) in the same transaction;

For the circles you can probably use CALayers and set the content, if it is an image, the standard way:

layer.contents = [UIImage imageNamed:@"circle_image.png"].CGImage;

Now, for the quad layer, subclass CALayer and implement this way:

- (void)drawInContext:(CGContextRef)theContext{
//Custom draw code here
}
+ (BOOL)needsDisplayForKey:(NSString *)key{
if ([key isEqualToString:@"customprop"])
return YES;
return [super needsDisplayForKey:key];
}

The transaction would look like:

[CATransaction begin];
CABasicAnimation *theAnimation=[CABasicAnimation animationWithKeyPath:@"customprop"];

theAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(1000, 1000)];
theAnimation.duration=1.0;
theAnimation.repeatCount=4;
theAnimation.autoreverses=YES;
theAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
theAnimation.delegate = self;
[lay addAnimation:theAnimation forKey:@"selecting"];

[CATransaction setValue:[NSNumber numberWithFloat:10.0f]
forKey:kCATransactionAnimationDuration];
circ1.position=CGPointMake(1000, 1000);
circ2.position=CGPointMake(1000, 1000);
[CATransaction commit];

Now all the draw routines will happen at the same time. Make sure your drawInContext: implementation is fast. Otherwise the animation will lag.

After adding each sublayer to the UIViews's layer, rememeber to call [layer setNeedsDisplay]. It does not get called automatically.

I know this is a bit complicated. However, the resulting animations are better than using a NSTimer and redrawing on each call.

Touch button with highlighting animation multiple times in a row

Try adding delay:0.0 options:UIViewAnimationOptionAllowUserInteraction to your other animateWithDuration: block. Or, you can simplify your code by combining both animation blocks like this:

Code:

-(void)setHighlighted:(BOOL)highlighted
{
[super setHighlighted:highlighted];

UIColor *colorToChangeTo = highlighted ? [UIColor darkGrayColor] : [UIColor clearColor];

[UIView animateWithDuration:0.1
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.backgroundColor = colorToChangeTo;
} completion:^(BOOL finished) {}];
}


Related Topics



Leave a reply



Submit