How do I change the setPosition of a layer mid animation - objective-c

I am currently animating an objects position to move across the x-axis in intervals of 50. However, I want to be able to stop the object and change its animation during certain circumstances, such as the user laying an object in front of it.
I have to use [self.layer setPosition:newPosition] or the object will bounce back to its original position after animation completes. If the object is dropped to stop it, the object bounces to the setPosition point before committing the new animation. How do I make it stop and stay where it is AND perform a new animation?

You are using implicit layer animation to animate the positions of your layers. To cancel an animation before it is complete, you would use:
[self.layer removeAllAnimations];
This would have the side effect of jumping your layer to the final animation position. If you want the layer to stop where it is, then you need to get the current presentationLayer position.
CGPoint currentAnimationPosition = self.presentationLayer.position;
self.layer.position = currentAnimationPosition;
[self.layer removeAllAnimations];
To perform a new animation, you can simply set a new position on the layer as you were doing before.

Related

Synchronizing CALayer drawing

A drawing application has multiple CALayers. One CALayer displays drawing objects, which are expensive to draw. An overlaid second CALayer displays hover effects - the drawing objects are highlighted on mousemove. This second CALayer is not expensive to draw.
When dragging drawing objects I call [CALayer setNeedsDisplay] on both of these CALayers. Each CALayer retrieves the current state of the drawing objects and displays them. As the drag goes on, the coordinates of the drawing objects change, but because the two layers are not drawn at the same time there's a coordinate mismatch. The difference is very visible in a fast drag.
Is there any CA way to synchronize drawing better between these two layers? It doesn't have to be perfect, but the lag needs to be much smaller. Making both a sublayer of another layer which is updated didn't work. Fiddling with asynchronous drawing didn't help either.
You could try to force the drawing right away by calling displayIfNeeded on the layer right after setNeedsDisplay.
For some reason just calling display did not work for me.

Mac OS: CAAnimation - seamlessly update the animation

I'm making custom UI element and I need to animate it. The problem is that at some point I need to update my animation, but when I update it, there is a visible lag/gap between current animation position (state) and new animation.
In other words, when I'm applying new animation, this animation starts from 0 position (state), while the actual (visible) state of the previous animation is different.
Here is example: (link)
<blockquote class="imgur-embed-pub" lang="en" data-id="a/q3NA31f"></blockquote><script async src="//s.imgur.com/min/embed.js" charset="utf-8"></script>
Code for animation:
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
anim.duration = time;
anim.repeatCount = INFINITY;
anim.fromValue = from;
anim.byValue = by;
[self.myLayer addAnimation:anim forKey:kActivityAnimationKey];
First applied animation have different time/from/by parameters.
ANY advices will be much appreciated, since I stuck on it on two days and tried everything that comes to head by this time.
I was able to finish this task and I will give a general answer to this question.
If you want to update your current ongoing animation, but you're in the middle of it, you need to replace current animation with animation that will end current animation on the right place.
For example, in my question there is an animation that have fromValue and byValue. It means, that if you'll start the same animation with different duration, it will change its current rotation state to fromValue.
To fix this behaviour, we need to add animation that will finish our animation from current rotation state to fromValue. To get your current rotation, or other property you need to animate, we have presentationLayer of CALayer. We can set the values from it to our "closing" animation.
How we can know when our closing animation is ended? The answer is CAAnimationDelegate. Just set up a delegate to the closing animation and when it's done - add another animation with different parameters. You can seamlessly update your animation now.
Keep in mind that closing animation time must be a fraction of your original animation time to make things smooth. You can calculate it like this: (end state - current state) * original duration.

Animating a circle UIImageView to disappear

If I have a UIImageView of a circle, how could Core Animation make it disappear like in this question?
How to use CoreAnimation to animate a circle like a clock countdown
Create a CAShapeLayer. Set its path to your “clock” shape. Set it as the layer mask of your image view (e.g. imageView.layer.mask = shapeLayer).
Then you can try using a CAKeyframeAnimation to animate the shape layer's path, but I suspect the results won't be pleasing. Instead you'll probably want to use a CADisplayLink as a timer and update the shape layer's path every time it fires.
What you are looking for is known as a "clock wipe" transition.
David is pointing you in the right direction. I have a sample project on Github that shows exactly what you are looking for:
iOS Core Animation Demo
When you run the app, click on the "Mask Animation" button. It does what you're talking about. It shows an image view with a clock wipe, and then hides it again. It would be a simple matter to switch it around to have the image fully visible at first and then animate a clock wipe to hide it.

drawInRect behavior with UIView rotation transform

I have a problem understanding how the parameter passed to the drawInRect method is defined when a rotation transformation is performed on a UIView.
To give an example I have a UIView which I rotated with an angle of 307 degree.
In the drawInRect method I log the following:
self.frame: {{103.932, 273.254}, {64.3007, 84.3374}}
rect (the variable passed as parameter:{{0, 0.102913}, {18, 89}}
The problem is that according to the documentation I should not draw outside of rect, but considering what I should draw, there is no way my images will fit there.
Can anyone explain to me how I am supposed to use drawInRect in the case my UIView is rotated ?
To give more detail about my problem, here is what I do:
I have a scrollview with a contentView inside (subclassed). I add my UIViews in the content view.
The views in question are composed of a handler image (bottom left) and the main image (top right). Users are supposed to grab the view by pressing the handler but that's not the point.
The drawInRect method of the UIView contains the following:
[_image drawInRect:CGRectMake(handlerSize.width, 0, _image.size.width, _image.size.height)];
CGSize size = CGSizeMake(kHandPickerWidth/self.scrollViewScale, kHandPickerHeight/self.scrollViewScale);
[_handPickerImage drawInRect:(0, _image.size.height, size.width, size.height)];
The UIViews objects are added at viewWillAppear in the content view doing the following:
first instanciate,
then addSubview:
then I set the scrollViewScale parameter,
then I set the frame parameter (according to the top right image displayed (which may vary)
then I rotate the UIView.
Starting from line three, the code is executed from the
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
to make sure every variable is set properly when displayed is needed.
The line defining the size variable is to adjust the marker's size no mater the zoomScale value of the scroll view.
You basically just draw as you would normally, and the painting will be rotated by iOS for you. You can get this transformation information if you would want to.
You need to get a reference to the currect graphics context:
CGContextRef ctx = UIGraphicsGetCurrentContext();
Then query the transformation matrix directly:
CGAffineTransform tf = CGContextGetCTM(ctx);
NSLog(#"current ctm: %#",NSStringFromCGAffineTransform(tf));
or better, get the transfrom from your drawing function to the device:
CGAffineTransform tf = CGContextGetUserSpaceToDeviceSpaceTransform(ctx);
NSLog(#"user->device transform: %#",NSStringFromCGAffineTransform(tf));
And in drawRect: you should not rely too much on the passed CGRect, because it serves mostly as a hint to what piece of the view needs updating. (e.g. because you called -setNeedsDisplayInRect: on it). To get the actual bounds where your view lives in, use self.bounds.
Drawing outside of the CGRect is no real problem, but will only hurt performance a little.
edit
ps. to clarify: self.frame is the frame of your view in the parent view coordinate system. It changes if you move, rotate or otherwise transform the view. self.bounds is the frame of your view in its own coordinate system, and (therefore) remains constant under changes of position or transformations.
So I found a solution to my problem:
I was setting the frame parameter multiple times with some CGAffineTransformation defined which is not supposed to be done.
Now each time I need to reset the frame I reset the affine transform, change the frame and set the back the affine transform.
Everything works as supposed to this way.

Animate the drawing of a line (Quartz 2D?)

How would you go about animating the drawing of a line in a UIView on the iPhone? Is this possible? It can be drawn but can it easily be animated so it appears to be hand drawn?
There's no built-in way to do this no. You'd have to redraw the line repeatedly, interpolating between the start and end points using a timer callback to invalidate the view and trigger a redraw. Of course the redraw would have to draw everything in the area of the view bring redrawn which is potentially slow.
What I would do if I had a series of lines I wanted to draw over a period of time is to have two subviews - they would cover the same area and the top one would have a transparent background. Have the top one draw just the line that I am currently animating and when it's finished, draw the full length of it in the lower view. Then repeat, animating the next line in the top view.
With the new API you can do that easily, using strokeEnd property of CGPath.
You can read more details here: What's the easiest way to animate a line?