I have an animation where a line is drawn and then with that another image moves. The animation works good but as the animation completes the image disappears from the view. The image should stay after completion of the animation. My code for the animation:
private func addPlaneAnimation() {
let image = UIImageView(frame: CGRect(x: -30, y: view.height*0.8, width: 30, height: 30))
image.image = R.image.plane()
view.addSubview(image)
let path = UIBezierPath()
path.move(to: image.center)
path.addLine(to: CGPoint(x: (view.width*0.85), y: (view.height*0.40)))
let shapeLayer = CAShapeLayer()
shapeLayer.fillColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0).cgColor
shapeLayer.strokeColor = R.color.Purple()?.cgColor
shapeLayer.lineWidth = 3
shapeLayer.path = path.cgPath
view.layer.addSublayer(shapeLayer)
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.duration = 4
animation.beginTime = CACurrentMediaTime() + 0.2
animation.isRemovedOnCompletion = false
let pathAnimation = CAKeyframeAnimation(keyPath: "position")
pathAnimation.path = path.cgPath
pathAnimation.duration = 4
pathAnimation.autoreverses = false
pathAnimation.isRemovedOnCompletion = false
shapeLayer.add(animation, forKey: "MyAnimation")
image.layer.add(pathAnimation, forKey: "position")
view.bringSubviewToFront(cardBarcode)
}
Can someone please help me here?
Just add two more properties.
For strokeEnd animation
animation.fillMode = CAMediaTimingFillMode.backwards
and for pathAnimation
pathAnimation.fillMode = CAMediaTimingFillMode.forwards
Related
Here is my code to draw dashed line
func drawLine() {
let shapeLayer = CAShapeLayer()
shapeLayer.bounds = viewDraw.bounds
shapeLayer.position = CGPoint(x: viewDraw.bounds.width/2, y: viewDraw.bounds.height/2)
shapeLayer.fillColor = nil
shapeLayer.strokeColor = ColorConstants.ThemeColor.cgColor
shapeLayer.lineWidth = 3
shapeLayer.lineJoin = CAShapeLayerLineJoin.round // Updated in swift 4.2
shapeLayer.lineDashPattern = [10]
let path = UIBezierPath(roundedRect: viewDraw.bounds, cornerRadius: viewDraw.bounds.size.height/2)
shapeLayer.path = path.cgPath
viewDraw.layer.addSublayer(shapeLayer)
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.duration = 1
shapeLayer.add(animation, forKey: "MyAnimation")
}
Now as per my screenshot I want to animate blue dot to red dot within that dashed line infinite time. When blue dot reaches red dot then it will start again with blue dot points.
Note: This whole screen is dynamic so, Line draw is also dynamic according to screen height
Any help would be appreciated
You are almost there
Now you have to add animation using CAKeyframeAnimation which has property path where you can pass the path where you want to animate
here is working sample code
func drawLine() {
let shapeLayer = CAShapeLayer()
shapeLayer.bounds = self.view.bounds
shapeLayer.position = CGPoint(x: self.view.bounds.width/2, y: self.view.bounds.height/2)
shapeLayer.fillColor = nil
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 3
shapeLayer.lineJoin = CAShapeLayerLineJoin.round // Updated in swift 4.2
shapeLayer.lineDashPattern = [10]
let path = UIBezierPath(roundedRect: self.view.bounds, cornerRadius: self.view.bounds.size.height/2)
shapeLayer.path = path.cgPath
self.view.layer.addSublayer(shapeLayer)
let animation1 = CABasicAnimation(keyPath: "strokeEnd")
animation1.fromValue = 0
animation1.duration = 1
shapeLayer.add(animation1, forKey: "MyAnimation")
// Create squareView and add animation
let animation = CAKeyframeAnimation(keyPath: #keyPath(CALayer.position))
animation.duration = 1
animation.repeatCount = MAXFLOAT
animation.path = path.cgPath
let squareView = UIView()
// Don't worry about x,y and width and height it will not affect the animation
squareView.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
squareView.backgroundColor = .lightGray
view.addSubview(squareView)
// You can also pass any unique string value for key
squareView.layer.add(animation, forKey: nil)
}
You can see this tutorial for more help http://mathewsanders.com/animations-in-swift-part-two/
I have a circle like in Instagram profile image which has story. I want to have an affect like circle is spinning. For that, I used CABasicAnimation. It is spinning but not in center.
As I searched, I need to give bounty for shapeLayer but when I do that, It doesn't placed where It needs to be.
How can I achieve animation like in Instagram story circle (like circle is spinning)?
EDIT I also try to add "colors" animation but because It works like It is in square, I can't get the desired result.
func addCircle() {
let circularPath = UIBezierPath(arcCenter: CGPoint(x: self.bounds.midX, y: self.bounds.midY), radius: self.bounds.width / 2, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
shapeLayer.strokeColor = UIColor.blue.cgColor
shapeLayer.lineWidth = 10
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineCap = kCALineCapRound
shapeLayer.strokeEnd = 1.0
gradient.frame = circularPath.bounds
gradient.colors = [UIColor.blue.cgColor, UIColor.red.cgColor]
gradient.startPoint = CGPoint(x: 0, y: 1)
gradient.endPoint = CGPoint(x: 1, y: 0)
shapeLayer.path = circularPath.cgPath
gradient.mask = shapeLayer
self.layer.addSublayer(gradient)
}
let rotationAnimation: CAAnimation = {
let animation = CABasicAnimation(keyPath: "transform.rotation")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 4
animation.repeatCount = MAXFLOAT
return animation
}()
#objc private func handleTap() {
print("Attempting to animate stroke")
shapeLayer.add(rotationAnimation, forKey: "urSoBasic2")
}
If spinning, why not spin gradient
let rotationAnimation: CAAnimation = {
let animation = CABasicAnimation(keyPath: "transform.rotation")
animation.fromValue = 0
animation.toValue = Float.pi * 2
animation.duration = 4
animation.repeatCount = MAXFLOAT
return animation
}()
#objc func handleTap() {
print("Attempting to animate stroke")
gradient.add(rotationAnimation, forKey: "urSoBasic2")
}
As to off center problem. The correct frame for gradient should be like this :
gradient.frame = self.bounds
To verify the center, you may add a background for the view:
self.background = UIColor.black.
The reason is width and height of the view is not set well due to the constraints. Try to set the correct bounds of the view, so when you add the circularPath, it's center in the view.
I am struggling with the strange issue like when I am adding scale transformation to any layer, it doesn't animated from the center. It does from origin.
func addSquareBorderLayer() {
borderLayer = CAShapeLayer()
let rect = CGRect(x: spotlight!.frame.origin.x, y: spotlight!.frame.origin.y, width: spotlight!.frame.width, height: spotlight!.frame.height)
borderLayer?.path = UIBezierPath(roundedRect: rect, cornerRadius: spotlight!.cornerRadius).cgPath
borderLayer?.strokeColor = UIColor(red: 91/255, green: 186/255, blue: 162/255, alpha: 1).cgColor //ColorRGB(91, 186, 162)
borderLayer?.lineWidth = spotlight!.cornerRadius
borderLayer?.fillColor = UIColor.clear.cgColor
layer.addSublayer(borderLayer!)
animatePulsatingLayer(pulsatingLayer: layer)
}
private func animatePulsatingLayer(pulsatingLayer : CALayer) {
let animation = CABasicAnimation(keyPath: "transform.scale")
animation.duration = 0.5
animation.repeatCount = .greatestFiniteMagnitude
animation.autoreverses = true
animation.fromValue = 1
animation.toValue = 1.05
pulsatingLayer.add(animation, forKey: "pulsing")
}
Can anybody please help?
Refernce to what's happening: https://www.dropbox.com/s/0hqdrrv310du7vz/mo.mov?dl=0
It's a bit tricky. You need to set the frame for the layer as the rect you are setting currently for path. And set path origin to zero so that the circle(or any shape) is drawn at the same position as frame's origin. This will allow the animation to be applied at the anchorPoint (0.5, 0.5).
Here is the complete example,
var borderLayer: CAShapeLayer!
func addSquareBorderLayer() {
let size: CGFloat = 100
borderLayer = CAShapeLayer()
let rect = CGRect(origin: .zero, size: CGSize(width: size, height: size))
borderLayer?.path = UIBezierPath(roundedRect: rect, cornerRadius: 50).cgPath
borderLayer?.strokeColor = UIColor(red: 91/255, green: 186/255, blue: 162/255, alpha: 1).cgColor
borderLayer?.lineWidth = 20
borderLayer?.fillColor = UIColor.clear.cgColor
borderLayer?.frame = CGRect(origin: CGPoint(x: 100, y: 200), size: rect.size)
self.view.layer.addSublayer(borderLayer!)
animatePulsatingLayer(pulsatingLayer: borderLayer!)
}
private func animatePulsatingLayer(pulsatingLayer : CALayer) {
let animation = CABasicAnimation(keyPath: "transform.scale")
animation.duration = 0.3
animation.repeatCount = .greatestFiniteMagnitude
animation.autoreverses = true
animation.fromValue = 1
animation.toValue = 1.2
pulsatingLayer.add(animation, forKey: "pulsing")
}
Result
let path = CGMutablePath()
path.move(to: CGPoint(x:0.0, y:10.5))
path.addLine(to: CGPoint(x: (self.frame.width/2), y:20.5))
let pathInit = CGMutablePath()
pathInit.move(to: CGPoint(x: 0, y:0))
pathInit.addLine(to: CGPoint(x: self.frame.width/2, y: 0))
// update the path property on the mask layer, using a CATransaction to prevent an implicit animation
let mask = CAShapeLayer()
mask.path = pathInit
let maskLayer: CAShapeLayer = CAShapeLayer()
maskLayer.path = pathInit
self.layer.mask = maskLayer
let anim = CABasicAnimation(keyPath: "path")
anim.fromValue = maskLayer.path
anim.toValue = path
anim.duration = 0.25
anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
maskLayer.add(anim, forKey: nil)
CATransaction.begin()
CATransaction.setDisableActions(true)
maskLayer.path = path
CATransaction.commit()
After I migrate to swift 3 this function not work, I think it because CABasicAnimation or CGMutablePath
please help,
In my app I have a checkmark inside a UIView which I reveal or conceal depending on the button the user clicks on to create the effect that the checkmark is being drawn or erased. I am using Bezier path with a mask to animate the checkmark being revealed or concealed with the code shown below. However, in the code to conceal it, the checkmark is being concealed from right to left and I would like to do it from left to right. What am I missing?
Code to reveal the checkmark
func checkmarkAnimationToReveal(view: UIView) {
let path = UIBezierPath(rect: CGRectMake(0, 0, view.bounds.width / 10, view.bounds.height))
let mask = CAShapeLayer()
mask.anchorPoint = CGPointMake(0.5, 1.0)
mask.path = path.CGPath
view.layer.mask = mask
let anim = CABasicAnimation(keyPath: "path")
anim.fromValue = path.CGPath
anim.toValue = UIBezierPath(rect:view.bounds).CGPath
anim.duration = 1.0
anim.fillMode = kCAFillModeForwards
anim.removedOnCompletion = false
view.layer.mask!.addAnimation(anim, forKey: "anim")
}
Code to conceal the checkmark
func checkmarkAnimationToConceal(view: UIView) {
CATransaction.begin()
CATransaction.setCompletionBlock({
print("completion reached")
self.checkmarkLabel.hidden = true
})
let path = UIBezierPath(rect: CGRectMake(0, 0, view.bounds.width / 10, view.bounds.height))
let mask = CAShapeLayer()
mask.anchorPoint = CGPointMake(0.5, 1.0)
mask.path = path.CGPath
view.layer.mask = mask
let anim = CABasicAnimation(keyPath: "path")
anim.fromValue = UIBezierPath(rect:view.bounds).CGPath
anim.toValue = path.CGPath
anim.duration = 1.0
anim.fillMode = kCAFillModeRemoved
anim.removedOnCompletion = false
view.layer.mask!.addAnimation(anim, forKey: "anim")
}