CALayer clip subviews in animation - swift

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.

Related

swift CAShapeLayer strokeColor by gradient color

hi i have a circle shapeLayer need color by GradientColor
i tried ues CAGradientLayer and mask it like this
let gradientChargeLayer = CAGradientLayer()
gradientChargeLayer.colors = CGColor.mColor
gradientChargeLayer.frame = vShape.bounds
gradientChargeLayer.locations = [0,1]
gradientChargeLayer.startPoint = CGPoint(x: 0,y: 0.5)
gradientChargeLayer.endPoint = CGPoint(x: 1,y: 0.5)
gradientChargeLayer.mask = shapeLayer
vShape.layer.addSublayer(gradientChargeLayer)
but what i want is gradient color from stroke start to end
shapeLayer.path = circularPath.cgPath
shapeLayer.strokeColor = UIColor.red.cgColor <<< here
thanks
you could’ve different approaches I believe
One, you could make an outer and inner circle and then add the required path/layer to the outer circle (this circle bigger than inner) both circles without visible stroke
Or I could recommend you UIGraphigsImageRenderer to work with
https://developer.apple.com/documentation/uikit/uigraphicsimagerenderer
This last have a context that could be used to do more advanced rendering context.cgContext.setStrokePattern here you can add a CGPattern and colors to trying accomplish it
PD. Sorry if my english is confuse
Shape layers only draw in a single color, as I guess you are aware.
By making your shape layer a mask onto a gradient layer you're able to have the shape layer reveal the color of the underlying gradient layer.
If you are always drawing a circle and want the color of the circle's stroke to vary from beginning to end you could set up your gradient layer using a type of conic, and specifying the colors in the gradient layer to begin and end where you want them. However, that would only work for shape layers that contain ovals, where the size and center of the shape and the size and the center of the gradient match.
As #Guillodacosta said in their answer, you may have to give up on using a shape layer and draw your graphic into a graphics context using Core Graphics. (You could do that once and capture the results into a UIImageView to avoid the slowdown of Core Graphics rendering.)

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.

iOS Swift UIBezierPath CAShapeLayer fillColor with animation

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

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.