I have added a mask to my view and am now trying to animate it. I have attempted to adapt the solution from https://stackoverflow.com/a/36461202/642502 but it's not animating for me.
I have tried to swap around where the animation is added and removing the setDisabledActions but continue to get the same results.
extension UIView {
public func mask(with rect: CGRect?, inverse: Bool = false, animated: Bool = true, duration: TimeInterval = 1.0) {
guard let rect = rect else {
self.layer.mask = nil
return
}
let path = UIBezierPath(rect: rect)
let maskLayer = CAShapeLayer()
if inverse {
path.append(UIBezierPath(rect: self.bounds))
maskLayer.fillRule = .evenOdd
}
if animated {
let animation = CABasicAnimation(keyPath: "path")
animation.fromValue = maskLayer.path
animation.toValue = path.cgPath
animation.duration = duration
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
animation.isRemovedOnCompletion = false
maskLayer.add(animation, forKey: "selectAnimation")
CATransaction.begin()
CATransaction.setDisableActions(true)
maskLayer.path = path.cgPath
self.layer.mask = maskLayer
CATransaction.commit()
} else {
maskLayer.path = path.cgPath
self.layer.mask = maskLayer
}
}
}
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/
All I do is:
let pathLayer = CAShapeLayer()
func drawBezierPath(from points: [CGPoint]) {
let path = UIBezierPath()
path.move(to: points.first!)
points.forEach { path.addLine(to: $0) }
pathLayer.path = path.cgPath
CATransaction.begin()
CATransaction.setCompletionBlock(block)
let strokeEndAnimation = CABasicAnimation(keyPath: "strokeEnd")
strokeEndAnimation.fromValue = 0
strokeEndAnimation.toValue = 1
let animation = CAAnimationGroup()
animation.animations = [strokeEndAnimation]
animation.duration = 1
pathLayer.add(animation, forKey: "PathAnimation")
CATransaction.commit()
}
The question is how to catch the moments when path is placed on the screen for every point of path. Why? I need to center scroll view to the current point. How can I do that?
I got this CAShapeLayer drawing a line in a chart for me:
open func generateLayer(path: UIBezierPath) -> CAShapeLayer {
let lineLayer = CAShapeLayer()
lineLayer.lineJoin = lineJoin.CALayerString
lineLayer.lineCap = lineCap.CALayerString
lineLayer.fillColor = UIColor.clear.cgColor
lineLayer.lineWidth = lineWidth
lineLayer.strokeColor = lineColors.first?.cgColor ?? UIColor.white.cgColor
lineLayer.path = path.cgPath
if dashPattern != nil {
lineLayer.lineDashPattern = dashPattern as [NSNumber]?
}
if animDuration > 0 {
lineLayer.strokeEnd = 0.0
let pathAnimation = CABasicAnimation(keyPath: "strokeEnd")
pathAnimation.duration = CFTimeInterval(animDuration)
pathAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
pathAnimation.fromValue = NSNumber(value: 0 as Float)
pathAnimation.toValue = NSNumber(value: 1 as Float)
pathAnimation.autoreverses = false
pathAnimation.isRemovedOnCompletion = false
pathAnimation.fillMode = kCAFillModeForwards
pathAnimation.beginTime = CACurrentMediaTime() + CFTimeInterval(animDelay)
lineLayer.add(pathAnimation, forKey: "strokeEndAnimation")
} else {
lineLayer.strokeEnd = 1
}
return lineLayer
}
Now I'd like to draw this line with a gradient instead of a single color. This is what I came up with, but unfortunately it does not draw the line for me. Without this added code (lineColors.count == 1) the line is drawn correctly with a single color.
fileprivate func show(path: UIBezierPath) {
let lineLayer = generateLayer(path: path)
layer.addSublayer(lineLayer)
if lineColors.count > 1 {
let gradientLayer = CAGradientLayer()
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
gradientLayer.frame = self.bounds
gradientLayer.colors = lineColors
gradientLayer.mask = lineLayer
layer.addSublayer(gradientLayer)
}
}
Well turns out I was searching more than an hour for this line:
gradientLayer.colors = lineColors
I forgot to map the UIColors objects in this array to CGColorRef objects...
This line fixed it for me:
gradientLayer.colors = lineColors.map({$0.cgColor})
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")
}