iOS Swift UIBezierPath CAShapeLayer fillColor with animation - swift

This is a Chinese word "夜". The word's outline build by UIBezierPath and CAShapeLayer.
like that:
I want to know, How to fill the word background color with the animation that like no.1 picture.
I know the word's medians position.
Do you have any idea? thanks.
some references:
https://github.com/chanind/hanzi-writer/blob/master/data/%E4%BB%B6.json
https://github.com/chanind/hanzi-writer
=== Update
let anim = CABasicAnimation(keyPath: "strokeEnd")
anim.fromValue = 0.0
anim.toValue = 1.0
That's not my want. #LouFranco

Related

iOS Fill half of UIBezierPath with other color without CAGradientLayer

I have a question about drawing half of a UIBezierPath. How do I fill left part (left from thumb) with green color and right part (right from thumb) with white color without using CAGradientLayer?
Code I used to create Bezier Path - https://gist.github.com/robertmryan/67484c74297cede3926a3aed2fceedb9
Screenshot of what I want to achieve:
One approach is to add a mask layer to your curved-path shape layer.
When the "thumb" position changes, change the width of the mask to reveal only the "left-side" of the shape layer.
Create a shape layer to use as the mask:
let maskLayer: CALayer = {
let layer = CALayer()
layer.backgroundColor = UIColor.black.cgColor
return layer
}()
In viewDidLoad() set that layer as the mask for the curved-shape layer:
pathLayer.mask = maskLayer
Whenever the "thumb" position is set, update the width of the mask:
func updateMask(at point: CGPoint) -> Void {
var f = view.bounds
f.size.width = point.x
CATransaction.begin()
CATransaction.setDisableActions(true)
maskLayer.frame = f
CATransaction.commit()
}
I posted a modified version of your gist at: https://gist.github.com/DonMag/a2154e70a3c67193a7b19bee41c8fe95
It really has only a few changes... look for comments beginning with // DonMag -
Here is the result (with an imageView behind it to show the transparency):
Edit
After comments, the goal is to have the "right-side" of the track path be white instead of transparent.
Using the same approach, we can add a white shape layer on top of the original shape layer, and mask it to show only the right-hand-side.
Here is an updated gist - https://gist.github.com/DonMag/397dfbe4779e817531ef7a663365b2e7 - showing this result:

CAShapelayer animate fill color from bottom

I have a CAShapelayer that's made from a bezier path, but the path is complex and not just a square.
I need to animate the shapelayer fill color to Yellow starting from the bottom of the curve and up (over y axis). (after the layer already attached with the path)
here is the path and layer curve:
If I use this code, it will animate all of the area at once.
let animation = CABasicAnimation(keyPath: "fillColor")
animation.fromValue = UIColor.white.cgColor
animation.toValue = curveFillColor!.cgColor
animation.duration = 4
animation.fillMode = .forwards
animation.isRemovedOnCompletion=false
shapeLayer.add(animation, forKey: "fillColor")
Shape layer code is :
let shapeLayer = CAShapeLayer()
shapeLayer.fillColor = UIColor.yellow.cgColor
shapeLayer.strokeColor = UIColor.yellow.cgColor
shapeLayer.lineWidth = 2.0
shapeLayer.lineJoin = CAShapeLayerLineJoin.round
shapeLayer.lineCap = CAShapeLayerLineCap.round
shapeLayer.strokeStart = 0
shapeLayer.path = path.cgPath
self.layer.addSublayer(shapeLayer)
I think you will probably have to create 2 versions of your shape layer: One filled using the starting color, and one filled with the ending color. Stack them on top of each other with the ending color layer on top. Then mask the ending color layer with a rectangular shape layer mask that has zero height. (meaning that the entire layer is masked.)
Now add an animation that animates the shape layer mask to the full height of your shape layer. That will reveal the ending color layer and cover the starting color layer, creating the effect that the ending color layer paints up from the bottom.
An alternative might be to rework your shape layer so that it that draws from the bottom to the top. To do that I guess you'd create a layer that was composed of a series of vertical lines drawn from bottom to top. Then you could animate the strokeStart or strokeEnd properties of your shape, but transforming your shape into a series of vertical lines would be a pain, and I don't know how you would do that for an arbitrary shape.

CALayer clip subviews in animation

I want to make animation, that should look like that:
there a layer on the mainViewLayer, drawn Drop of water with transparent fillColor, I add under it another CAShapeLayer - with blue Color, there layers have the same bounds. and i want to animate its y position, that will imitate decreasing of water level.
fallingWaterLayer = CAShapeLayer()
fallingWaterLayer.frame = x.bounds
fallingWaterLayer.fillColor = UIColor.blue.cgColor
let path = CGPath(rect:fallingWaterLayer.bounds, transform:nil)
fallingWaterLayer.path = path
x.layer.addSublayer(fallingWaterLayer)
mainLayer = DropLayer(x.bounds, shouldFillLayer:false)
x.layer.addSublayer(mainLayer!)
my animation looks like that:
let animation = CABasicAnimation(keyPath: "bounds.origin.y")
animation.toValue = x.bounds.size.width * -1
animation.duration = 5.8
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
animation.fillMode = kCAFillModeBoth
animation.isRemovedOnCompletion = false
fallingWaterLayer.add(animation, forKey: animation.keyPath)
So the question is how can i make top Layer clips the blue rect, to make it look like animation is done inside the Drop of water?
Every CALayer has a mask property, which can be any arbitrary other layer, so that seems like it would be a good solution.
https://developer.apple.com/reference/quartzcore/calayer/1410861-mask
The way the mask property works is that any transparent pixel of the mask will be clipped in the target layer. So you'll need to create a second, solid water drop layer to use as the mask - you can't re-use the same water drop outline, otherwise the water will be clipped to just the border of the water drop, instead of filling the inside.

How to trigger an animation by PERCENTAGE in swift?

Alright, I am trying to trigger an animation incrementally as a scroll view is scrolled. To do this, I have taken this link here and converted it to swift - http://www.oliverfoggin.com/controlling-animations-with-a-uiscrollview/
Giving me the percentage offset x for my scrollview. This is all great.
Problem is I'm fairly new to swift and don't know how to tie this back into my existing animation which is a transform/move instead of changing color.
Here's my animation here-
self.borderlines.transform = CATransform3DMakeScale(CGFloat(0.01), CGFloat(1.0), 1)
self.activeBorder.layer.opacity = 1
CATransaction.begin()
self.activeBorder.layer.transform = CATransform3DMakeScale(CGFloat(0.03), CGFloat(1.0), 1)
let anim2 = CABasicAnimation(keyPath: "transform")
let fromTransform = CATransform3DMakeScale(CGFloat(0.01), CGFloat(1.0), 1)
let toTransform = CATransform3DMakeScale(CGFloat(1.0), CGFloat(1.0), 1)
anim2.fromValue = NSValue(CATransform3D: fromTransform)
anim2.toValue = NSValue(CATransform3D: toTransform)
anim2.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
anim2.fillMode = kCAFillModeForwards
anim2.removedOnCompletion = false
self.activeBorder.layer.addAnimation(anim2, forKey: "_activeBorder")
CATransaction.commit()
And then I also have another move animation. As is these are called on touch, so 1 static event.
The guy in the tutorial from what I can see recalculated/triggers his animation EVERY scroll then alters bg color by percentage. I don't know how to apply this to another type of animation- he mentions key frames and I have no idea what those are.
How can I achieve this effect? What do I need to change here?
Look into "freezing" your animation by setting its layer's speed to zero and manipulating its timeOffset as your response to scrolling.

CABasicAnimation with keypath "bounds" not working

I have the following code to animation bounds property of CALayer using CABasicAnimation. But the code doesn't seem to work.
let fromValue = textLabel.layer.bounds
let toValue = CGRectMake(textLabel.layer.bounds.origin.x, textLabel.layer.bounds.origin.y, textLabel.layer.bounds.width, textLabel.layer.bounds.height + 50)
let positionAnimation = CABasicAnimation(keyPath: "bounds")
positionAnimation.fromValue = NSValue(CGRect: fromValue)
positionAnimation.toValue = NSValue(CGRect: toValue)
positionAnimation.duration = 1
positionAnimation.fillMode = kCAFillModeBoth
positionAnimation.removedOnCompletion = false
textLabel.layer.addAnimation(positionAnimation, forKey: "bounds")
Your code does actually work. If you run your code and then switch on View Debugging in Xcode, you will see that the height of the label has increased. The "problem" is that a UILabel in iOS 8 draws itself (its text and its background, if it has one) the same way even after its layer height has been artificially increased in this way. (I believe that this is because the label draws itself with a special clipping region that is based on its text contents.)
To prove this to yourself, try it on a plain vanilla UIView (with a colored background) instead of a label. I've taken the liberty of cleaning up your code (you should never misuse fillMode and removedOnCompletion the way you are doing - it just shows a lack of understand of what animation is):
let fromValue = view2.layer.bounds.height
let toValue = view2.layer.bounds.height + 50
CATransaction.setDisableActions(true)
view2.layer.bounds.size.height = toValue
let positionAnimation = CABasicAnimation(keyPath: "bounds.size.height")
positionAnimation.fromValue = fromValue
positionAnimation.toValue = toValue
positionAnimation.duration = 1
view2.layer.addAnimation(positionAnimation, forKey: "bounds")
You will see that that works perfectly. Now change view2 back to textLabel throughout. It still works, it's just that there is nothing to see.
Another way to prove this to yourself is to drop the whole animation and just change the label's layer height:
self.textLabel.layer.bounds.size.height += 50
You will not see anything happen. So there is no bug in your animation; it's all about the way labels are drawn.
You can make the change visible by changing the view instead of the layer:
self.textLabel.bounds.size.height += 50
One additional "problem", though, is that the animation is not animating. Again this is because labels are drawn in a special way.
So whatever it is you're trying to accomplish it, you'll have to do it in a different way. You might have a clear label in front of a colored view and animate the change in height of the view; we've already proven that that works.