Collision detection & animateWithDuration - ios

I am trying to get 'luke' to jump over an object to ensure that the game does not end, which he does but even if he avoids the object the game still ends, as if luke had not left his original location during the animation.
I used the UIView animateWithDuration in -(void)touchesBegin to try to achieve this.
[UIView animateWithDuration:3
animations:^
{
luke.center = CGPointMake(luke.center.x +0, luke.center.y -60);
}
completion:^(BOOL completed)
{
if (completed)
{
[UIView animateWithDuration:3
animations:^
{
luke.center = CGPointMake(luke.center.x +0, luke.center.y +60);
}];
}
I am also using CGRectIntersectsRect to tell me when the two objects collide
-(void)collision {
if (CGRectIntersectsRect(luke.frame, smallboulder.frame)) {
[self endgame];
}
if (CGRectIntersectsRect(luke.frame, largeboulder.frame)) {
[self endgame];
}
if (CGRectIntersectsRect(luke.frame, cactus.frame)) {
[self endgame];
}
finally can i use an animation set up as a NSArray to show movement when jumping.
Many thanks
JP

When using UIView animation methods to animate various properties, the animation is actually performed on something called the presentation layer. But when you use the view's frame accessor, it accesses the model layer (rather than the presentation layer), which holds the latest values (so it holds the designated frame after animation).
You should check for "luke"'s location using luke.layer.presentationLayer.frame.

Related

Force UIView animateWithDuration completion block to be called

I have two animations which are overlapping, which, because of the way I've set my method up, causes the second one not to fire. I have a check like so in the beginning of the method:
- (void)animateHidden:(BOOL)hidden duration:(CGFloat)seconds delay:(CGFloat)delay options:(UIViewAnimationOptions)options disableUserInteraction:(BOOL)disableUserInteraction {
if (self.hidden == hidden) {
return;
}
Then, further down, my animation block looks like so:
__weak UIView *weakSelf = self;
[UIView animateWithDuration:seconds delay:delay options:options animations:^{
weakSelf.alpha = hidden ? 0 : 1;
} completion:^(BOOL finished) {
// Return user interaction to previous state
if (disableUserInteraction) {
weakSelf.userInteractionEnabled = userInteractionEnabled;
}
weakSelf.hidden = hidden;
}];
Two animations are kicked off on the same view, one before a service call and one after. If the service call happens quick enough that the view is still animating, weakSelf.hidden = hidden; will never be called, and the second animation will exit out since the hidden value wasn't updated in time.
Is there anyway that I could force the completion block on the animation block to be called? I need to update my hidden property before making the check, but can't find a way to accomplish this.
Calling [self.layer removeAllAnimations] doesn't seem to work unfortunately.
You can use CABasicAnimation instead of UIView animation, that will solve the problem more accurately.
You can use it like:
CABasicAnimation* opacityZero= [CABasicAnimation animationWithKeyPath:#"opacity"];
[opacityZero setToValue:[NSNumber numberWithFloat:0.0]];
[opacityZero setDuration:duration];
[[self layer] addAnimation:opacityZero forKey:#"opacityZero"];
And when your service call ends, you can call [self.layer removeAllAnimations];
Similarly, you can make the opacity one and tweak the above method as you like.
You can find more info here.
A __block prefixed to hidden attribute declaration should help.
Something like, #property (nonatomic) __block BOOL hidden;
If you are targeting iOS 10+ take a look at UIViewPropertyAnimator
https://developer.apple.com/reference/uikit/uiviewpropertyanimator?language=objc
Combined with the UIViewAnimating and UIViewImplicitlyAnimating protocols, this allows modification / interruption / pause / resume / stop / etc of the animations.
Basic example (buttons and view set in IB):
- (IBAction)startTapped:(id)sender {
_myAnimator = [UIViewPropertyAnimator
runningPropertyAnimatorWithDuration:3.0
delay:0.0
options:UIViewAnimationOptionCurveLinear
animations:^{
_theRedBox.alpha = _theRedBox.alpha > 0 ? 0 : 1;
} completion:^(UIViewAnimatingPosition finalPosition) {
// do stuff
}];
}
- (IBAction)stopTapped:(id)sender {
[_myAnimator stopAnimation:NO];
[_myAnimator finishAnimationAtPosition:UIViewAnimatingPositionEnd];
}

Custom Interactive transition with CABasicAnimation(CAAnimation)

I want build interactive transition with UIViewAnimation.But there's few layer property that I can animate it.So i decided use the CAAnimation.
I wanna change the ViewController's view mask,Here is the code
-(NSTimeInterval)transitionDuration:(nullable id<UIViewControllerContextTransitioning>)transitionContext{
return 0.5f;
}
-(void)animateTransition:(nonnull id<UIViewControllerContextTransitioning>)transitionContext{
_transitionContext=transitionContext;
UIViewController *fromVC=
[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC=
[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *containerView=[transitionContext containerView];
_toVC=toVC;
_fromVC=fromVC;
[containerView insertSubview:toVC.view aboveSubview:fromVC.view];
//Create the BezierPath
UIBezierPath *initailPath=[UIBezierPath bezierPathWithRect:(CGRect) {{_cellRect.size.width/2,_cellRect.origin.y+_cellRect.size.height/2},.size= {0.5,0.5}}];
CGFloat radius;
CGFloat distance;
if (fromVC.view.frame.size.width>fromVC.view.frame.size.height) {
distance=fromVC.view.frame.size.width-_cellRect.origin.x;
radius=distance>_cellRect.origin.x?distance:_cellRect.origin.x+88;
}else{
distance=fromVC.view.frame.size.height-_cellRect.origin.y;
radius=distance>_cellRect.origin.y?distance:_cellRect.origin.y+88;
}
radius=radius*2;
UIBezierPath *finalPath=[UIBezierPath bezierPathWithOvalInRect:CGRectInset(_cellRect,
- radius,
- radius)];
_initaialPath=initailPath;
_finalPath=finalPath;
//Create a Layer Mask
_maskLayer=[[CAShapeLayer alloc] init];
_maskLayer.path=finalPath.CGPath;
toVC.view.layer.mask=_maskLayer;
[self animateLayer:_maskLayer withCompletion:^{
BOOL isComple=![transitionContext transitionWasCancelled];
if (!isComple) {
[containerView addSubview:fromVC.view];
[toVC.view removeFromSuperview];
}
[transitionContext completeTransition:isComple];
}];
}
-(void)startInteractiveTransition:(nonnull id<UIViewControllerContextTransitioning>)transitionContext{
_transitionContext=transitionContext;
[self animateTransition:transitionContext];
[self pauseTime:[_transitionContext containerView].layer];
}
This is the main animation,it work perfectly without interactive.
Then i tried to control it with these code:
-(void)updateInteractiveTransition:(CGFloat)percentComplete{
[_transitionContext updateInteractiveTransition:percentComplete];
[_transitionContext containerView].layer.timeOffset=_pausedTime + [self transitionDuration:_transitionContext]*percentComplete;
}
-(void)finishInteractiveTransition{
[_transitionContext finishInteractiveTransition];
[self resumeTime:[_transitionContext containerView].layer];
}
These two functions work perfectly
Buthere is the Problem with "Cancel Transition"
When i cancel the transition it disappear suddenly
(I have tried with the solution in this question But That's not work for me)
here is now my code:
- (void)cancelInteractiveTransition {
//Must Cancel System InteractiveTransition FRIST
[_transitionContext cancelInteractiveTransition];
//Then adjust the layer time
CALayer *maskLayer =[_transitionContext containerView].layer;
maskLayer.beginTime = CACurrentMediaTime();
//MOST IMPORTANT
[UIView animateWithDuration:0.5 animations:^{
maskLayer.timeOffset=0.0;
} completion:^(BOOL finished) {
[[_transitionContext containerView ] addSubview:_fromVC.view];
[_toVC.view removeFromSuperview];
}];
}
Now I almost spend whole week on this, If you can help me I really predicate that.
We can easily animate the layer's property from the initial value to the final value.
Generally when we want implement a interactive animation, we can just use the UIView's animation block to reach this(Go to final position or back to original position). But you can not do this with some property like BackgroundColour,CGPath.
Acturlly we can control the process by using CAAnimation, and control the
timeoffset of CALayer. When we control the animation process in some precise position. how can we make it back to the animation process? If the property you controlled is position.
[UIView animateWithDuration:/*Left time*/
delay:/*delay time*/
usingSpringWithDamping:/*damping*/
initialSpringVelocity:/*speed*/
options:/*option*/
animations:^{
/*Your animation*/
} completion:^(BOOL finished) {
}];
But it's not suitable for something like CGPath or BackgroundColor.
We need a way to drive the animation, apple have supported a Driver, this driver will call a function,that you specified.And it will perform it 60times per second just like iPhone screen.
The tech is CADisplayLink object. It is a timer object that allows your application to synchronise your drawing to the refresh rate of the display.
Then I got the solution:
if (!_displayLink) {
_displayLink=[CADisplayLink displayLinkWithTarget:self selector:#selector(animationTick:)];
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
AnimationTick Function is your function to refresh:
-(void)animationTick:(CADisplayLink *)displayLink{
CALayer *maskLayer=[_transitionContext containerView].layer;
CGFloat timeOffset=maskLayer.timeOffset;
timeOffset=MAX(0,timeOffset-_piceDistance);
maskLayer.timeOffset=timeOffset;
if (timeOffset==0) {
displayLink.paused=YES;
}
}
That's all

iOS: UIView Animation Refactoring

Whilst playing around with UIView animation, I came across a situation where I think some refactoring is needed:
The following views whose opacity are initially set to 0.0f.
Ex:
[UIView animateWithDuration:1.0f
animations:^
{
firstView.layer.opacity = 1.0f;
}
completion:^(BOOL finished)
{
[UIView animateWithDuration:1.0f
animations:^
{
secondView.layer.opacity = 1.0f;
firstView.layer.opacity = 0.0f;
}
completion:^(BOOL finished)
{
[UIView animateWithDuration:1.0f
animations:^
{
thirdView.layer.opacity = 1.0f;
secondView.layer.opacity = 0.0f;
}
completion:^(BOOL finished)
{
thirdView.layer.opacity = 0.0f;
}];
}];
}];
All 3 views are just subclass of UIView's, which are added as subviews of the main view.
This simply animates the opacity of the first view to 1.0f and then that of the second view, and then that of the third view.
Simple. Nothing special here.
My Question is:
What if I had more views, let say 100, that I wanted to perform the same action (same sequence of animation), this block of code would expand and expand.
So for the sake of refactoring and being adhered to good practice of writing code, I thought may be this could be done with less code via the use of a method and perhaps a loop.
Could you enlighten me on this in regards to refactoring; in addition, would dispatch_apply be useful here along with the refactoring process if a loop is needed?
If you wanted to animate 100 images, you would probably want to use 2 views and load alternating images into each one. I recently created a sample app on github that does exactly that:
Animating UIImages with cross-fade opacity changing

dynamically change images in an animation IOS

I have a requirement to animate images . I have a large number of images and this needs to be played as an video. In between playing sometimes i need to change some images as they will be updated at server. so playing should automatically update this new images .
I have tried using UIImageView. There we cannot control the animation.
I then tried CAKeyframeAnimation supplying image array to values property. I could play and pause the animation. But here also i cannot dynamically change the image while playing.
Can anyone help me in solving this problem.
Thanks
mia
The animation system on UIImageView is very limited. I would suggest that you make your own implementation with say 2 image view.
You would than change the image in one imageview and the fade in and out using UIView animateWithDuration
I have written a method for you. Please note: I have not tested it.
It assumes you have your frames in a array called 'frames' and have two UIIMageView placed on top of each other called 'imgv1' and 'imgv2'
-(void)performAnimationOfFrameAtIndex:(NSNumber*)indexNum
{
int index = [indexNum intValue];
if(index >= frames.count)
{
return;
}
UIImage *img = [frames objectAtIndex:index];
UIImageView *imgIn;
UIImageView *imgOut;
if(index % 2 == 0)
{
imgIn = imgv1;
imgOut = imgv2;
}
else {
imgIn = imgv2;
imgOut = imgv1;
}
imgIn.image = img;
[self.view sendSubviewToBack:imgIn];
[UIView animateWithDuration:0.1 animations:^{
imgIn.alpha = 1;
imgOut.alpha = 0;
} completion:^(BOOL finished) {
[self performSelectorOnMainThread:#selector(performAnimationOfFrameAtIndex:) withObject:[NSNumber numberWithInt:index+1] waitUntilDone:NO];
}];
}
Use two UIImageViews and swap them.
If you have a weak reference on the "animation" UIView, be sure to check if animation has finished in the completion block. Otherwise you may experience performance issues when you navigate to another view, recursive calls to animation method will continue!
- (void)animateImagesAtIndex:(NSNumber *)imageIdx {
// Do something here
[UIView animateWithDuration:1 animations:^{
// Do swap
}
completion:^(BOOL finished) {
if (finished) {
[self animateImagesAtIndex:imageIdx];
}
}];
}
UIImageView supports animations for a series of images. You only have to set the properties animationImages with an array of the images, and call the methods startAnimating and stopAnimating.

Issue rotating a UIView object using Core Animation and blocks

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;
}

Resources