I'm trying to animate an object along a specific segment of a path using CAKeyframeAnimation. Is this possible?
Think of a path as parameterized between 0 and 1, which is what an animation will typically run on (from the start of the curve to the end of the curve). Is there a way I can run an animation, say, from .25 to .75 of this path?
I know I can offset an animation with the 'offset' parameter, but this only adjusts the start position and still loops around to the end of the curve. It also doesn't correctly obey the CAMediaTiming function, if set.
Related
I created a door with ARKit and I want create a scale animation. My goal is to scale it only along the y-axis (stretch the door to be longer).
I want the door to grow within a duration of 1 second.
My approach was to simply scale it but I only have options that allow me to scale my entire object along all 3 axis.
Next I tried node.scale = SCNVector3(0, 2, 0) and that works ok, but it has no nice animation to it. When I create a SCNAction() and run the code as a block with duration time, it still just changes the size without any smooth animation.
You need to use SCNTransaction. The simplest way to animate your node scaling would be something like this:
SCNTransaction.begin()
SCNTransaction.animationDuration = 3
node.scale = SCNVector3(0, 2, 0)
SCNTransaction.commit()
You might also need to modify pivot property of the node to position the animation correctly.
I'm trying to achieve something similar to the attached image, where the circle is animated depending on your level progress and then a label is attached to both the end of the animated path to show the gained experience and also the undrawn part of the circle to show the remaining experience. I have the circle animating as wanted but am having trouble coming up with a solution to the labels to appear in the right spot.
I've tried setting the position of the label to the path.currentPoint but that always seems to be the start of the drawn path and not the end.
Any pointers on how to achieve this would be great!
I had been working on your question, first of all to achieve this you must animate the path the real path, not only the strokeEnd, if you animate only the strokeEnd your path.currentPoint will always return the circle endPoint of the path, in order to animate the path you need to make a KeyFramed Animation animating the “path” as keyPath and passing an array of paths from current angle to desired one, then in order to set the correct position for your label you need to get de currentPoint of all this paths values and making another keyFramed animation with “position” keyPath and passing as values all this points collected from paths array
This is a basic example working
The code is in GitHub in this Repo
You have a lot of work to do yet but, this can be an starting point for your final solution
Hope this helps, best regards
Given following view hierarchy:
root (e.g. view of a view controller)
|_superview: A view where we will draw a cross using core graphics
|_container: Clips subview
|_subview: A view where we will show a cross adding subviews, which has to align perfectly with the cross drawn in superview
|_horizontal line of cross
|_vertical line of cross
Task:
The crosses of superview and subview have to be always aligned, given a global transform. More details in "requirements" section.
Context:
The view hierarchy above belongs to a chart. In order to provide maximal flexibility, it allows to present chart points & related content in 3 different ways:
Drawing in the chart's base view (superview) draw method.
Adding subviews to subview. subview is transformed on zoom/pan and with this automatically its subviews.
Adding subviews to a sibling of subview. Not presented in view hierarchy for simplicity and because it's not related with the problem. Only mentioning it here to give an overview. The difference between this method and 2., is that here the view is not transformed, so it's left to the implementation of the content to update "manually" the transform of all the children.
Maximal flexibility! But with this comes the cost that it's a bit tricky to implement. Specifically point 2.
Currently I got zoom/pan working by basically processing the transforms for superview core graphics drawing and subview separately, but this leads to redundancy and error-proneness, e.g. repeated code for boundary checks, etc.
So now I'm trying to refactor it to use one global matrix to store all the transforms and derive everything from it. Applying the global matrix to the coordinates used by superview to draw is trivial, but deriving the matrix of subview, given requirements listed in next section, not so much.
I mention "crosses" in the view hierarchy section because this is what I'm using in my playgrounds as a simplified representation of one chart point (with x/y guidelines) (you can scroll down for images and gists).
Requirements:
The content can be zoomed and panned.
The crosses stay always perfectly aligned.
subview's subviews, i.e. the cross line views can't be touched (e.g. to apply transforms to them) - all that can be modified is subview's transform.
The zooming and panning transforms are stored only in a global matrix matrix.
matrix is then used to calculate the coordinates of the cross drawn in superview (trivial), as well as the transform matrix of subview (not trivial - reason of this question).
Since it doesn't seem to be possible to derive the matrix of subview uniquely from the global matrix, it's allowed to store additional data in variables, which are then used together with the global matrix to calculate subview's matrix.
The size/origin of container can change during zoom/pan. The reason of this is that the labels of the y-axis can have different lengths, and the chart is required to adapt the content size dynamically to the space occupied by the labels (during zooming and panning).
Of course when the size of container changes, the ratio of domain - screen coordinates has to change accordingly, such that the complete original visible domain continues to be contained in container. E.g if I'm displaying an x-axis with a domain [0, 10] in a container frame with a width of 500pt, i.e. the ratio to convert a domain point to screen coordinates is 500/10=50, and shrink the container width to 250, now my [0, 10] domain, which has to fit in this new width, has a ratio of 25.
It has to work also for multiple crosses (at the same time) and arbitrary domain locations for each. This should happen automatically by solving 1-7 but mentioning it for completeness.
What I have done:
Here are step-by-step playgrounds I did to try to understand the problem better:
Step 1 (works):
Build hierarchy as described at the beginning, displaying nothing but crosses that have to stay aligned during (programmatic) zoom & pan. Meets requirements 1, 2, 3, 4 and 5:
Gist with playground.
Particularities here:
I skipped container view, to keep it simple. subview is a direct subview of superview.
subview has the same size as superview (before zooming of course), also to keep it simple.
I set the anchor point of subview to the origin (0, 0), which seems to be necessary to be in sync with the global matrix.
The translation used for the anchor change has to be remembered, in order to apply it again together with the global matrix. Otherwise it gets overwritten. For this I use the variable subviewAnchorTranslation. This belongs to the additional data I had in mind in the bullet under requirement 5.
Ok, as you see everything works here. Time to try the next step.
Step 2 (works):
A copy of step 1 playground with modifications:
Added container view, resembling now the view hierarchy described at the beginning.
In order for subview, which is now a subview of container to continue being displayed at the same position, it has to be moved to top and left by -container.origin.
Now the zoom and pan calls are interleaved randomly with calls to change the frame position/size of container.
The crosses continue to be in sync. Requirements met: All from step 1 + requirement 6.
Gist with playground
Step 3 (doesn't work):
So far I have been working with a screen range that starts at 0 (left side of the visible playground result). Which means that container is not fulfilling it's function to contain the range, i.e. requirement 7. In order to meet this, container's origin has to be included in the ratio calculation.
Now also subview has to be scaled in order to fit in container / display the cross at the correct place. Which adds a second variable (first being subviewAnchorTranslation), which I called contentScalingFactor, containing this scaling, that has to be included in subview's matrix calculation.
Here I've done multiple experiments, all of them failed. In the current state, subview starts with the same frame as container and its frame is adjusted + scaled when the frame of container changes. Also, subview being now inside container, i.e. its origin being now container's origin and not superview's origin, I have to set update its anchor such that the origin is not at (0,0) but (-x,-y), being x and y the coordinates of container's origin, such that subview continues being located in relation to superview's origin. And it seems logical to update this anchor each time that container changes its origin, as this changes the relative position from content's origin to superview's origin.
I uploaded code for this - in this case a full iOS project instead of only a playground (I thought initially that it was working and wanted to test using actual gestures). In the actual project I'm working on the transform works better, but I couldn't find the difference. Anyway it doesn't work well, at some point there are always small offsets and the points/crosses get out of sync.
Github project
Ok, how do I solve this such that all the conditions are met. The crosses have to stay in sync, with continuous zoom/pan and changing the frame of container in between.
The present answer allows for any view in the Child hierarchy to be arbitrarily transformed. It does not track the transformation, merely converts a transformed point, thus answers the question:
What are the coordinates of a point located in a subview in the coordinate system of another view, no matter how much that subview has been transformed.
To decouple the Parent from the clipping Container and offer a generic answer, I propose place them at the same level conceptually, and in different order visually (†):
Use a common superview
To apply the scrolling, zooming or any other transformation from the Child to the Parent, go through common superview (named Coordinator in the present example).
The approach is very similar to this Stack Overflow answer where two UIScrollView scroll at different speed.
Notice how the red hairline and black hairline overlap, regardless of the position, scrolling, transform of any and all off the views in the Child hierarchy, including that of Container.
↻ replay animation
Code
Coordinate conversion
Simplified for clarity, using an arbitrary point (50,50) in the coordinate system of the Child view (where that point is effectively drawn), and convert it in the Parent view system looks like this:
func coordinate() {
let transfer = theChild.convert(CGPoint(x:50, y:50), to: coordinator)
let final = coordinator.convert(transfer, to: theParent)
theParent.transformed = final
theParent.setNeedsDisplay()
}
Zoom & Translate Container
func zoom(center: CGPoint, delta: CGPoint) {
theContainer.transform = theContainer.transform.scaledBy(x: delta.x, y: delta.y)
coordinate()
}
func translate(delta: CGPoint) {
theContainer.transform = theContainer.transform.translatedBy(x: delta.x, y: delta.y)
coordinate()
}
(†) I have renamed Superview and Subview to Parent and Child respectively.
Having an issue with farseer physics im pretty sure the issue is just with this line of code because of the y value. So when i try to move my character when falling he moves left and right normally but his fall slows.
body.LinearVelocity = new Vector2(1,0)
Is there a way to only change the x value of this? Or is there a way to prevent sliding and set a cap on the speed of applyforce() or applylinearimpulse()?
By setting the linear velocity to 1,0 you give the character a horizontal speed of 1 and a vertical speed of 0. So you effectively stop it from falling.
The following code will do what you would expect as it preserves the vertical speed.
body.LinearVelocity = new Vector2(1, body.LinearVelocity.y);
In some (most) cases it is better to apply a force or impulse to the character using body.Apply... this applies a force for a single frame and Farseer will automatically compute the correct velocity. Note that adding the same force or impulse every frame will make the movement speed-up.
Within a UISrollView, I have several programmatically-added subviews representing nodes in a tree:
Each node's frame includes the node itself plus the line connecting it to its parent. (I did this to facilitate animation of the line with the node.) In the picture below, the frame is drawn for one of the nodes:
When the user taps on one of the nodes, two child nodes are "born". I'd like to animate this by having the child nodes descend down from behind the parent node. My basic animation code is:
- (void)descendFromParent
{
// Do nothing if this is root node
if (!self.parent)
return;
// Move node to parent location
self.frame = CGRectMake(self.frame.origin.x + self.parent.nodeFrame.origin.x - self.nodeFrame.origin.x,
self.frame.origin.y + self.parent.nodeFrame.origin.y - self.nodeFrame.origin.y,
self.frame.size.width, self.frame.size.height);
// Animate the move back to original location
[UIView animateWithDuration:1.0
delay:0.0
options:0
animations:^{
self.frame = self.trueFrame;
}
completion:nil];
}
(nodeFrame is a frame containing just the circular part of the view.)
The problem of course is that as the child node is descending, it (especially the line) is visible on top of and above the parent node:
I've tried a lot of different ways to make this work -- using CGContextClip, etc. -- but none of them work, mainly because drawRect: isn't called during the animation.
How can I make this work?
I think your problem is not how to arrange the views in the correct view hierarchy, but the following:
After you have arranged the child disks behind the parent disk, you want them to slide down, and while they are sliding, the edge that connects their centers should have first zero length (when all 3 disks are at the same place), and should then be extended until it reaches its final length in the end.
If this is the case, one possible solution would be:
Lets call the initial x,y center coordinate of one disk (0,0), and the final coordinate (X,Y). Using the usual animation code, it is easy to move the child center to (X,Y) in time t.
Now assume you have an additional image view that shows the edge along the diagonal. In the end position, the center of the "edge view" is at (X/2,Y/2), and its size is (X,Y). If this view is placed behind all others, it will connect the two disks at their final position.
The animation consists now of 1) changing the center property from the initial position (0,0) to (X/2,Y/2), and 2) changing the scale of the view (using its transform property) from zero to the final size, also in time t.
This should do it.
You may want to look at the UIView methods
-insertSubview:belowSubview:
-insertSubview:atIndex:
-sendSubviewToBack:
In your case, you can either send the two subviews to the background with -sendSubviewToBack: after you have added them. Or you can just add them with -insertSubview:atIndex: and provide 0 as the index.
You can specific an index for a subview, try inserting the nodes you want to animate at index 0 and have the blue nodes at a higher index.