merge two CAShaplayer with transparent in Swift5? - ios

We have created two CAShapeLayer and we have animated them by CABasicAnimation changing position like fromValue to toValue so far code works as excepted.
Two layers meeting at some points while animating, its moving one over another seems overlapping. We need to achieve like below image but we have got different output.
Desired output:
Output at Loading view::
Result we got after animating layers:
Code Below:
class MovingAnimationVC: UIViewController {
let layerSize = CGSize(width: 100, height: 100)
private var layerWidth : CGFloat = 100
private var layerHeight : CGFloat = 100
var circleA : CAShapeLayer! = nil
var circleB : CAShapeLayer! = nil
lazy var aPosition = CGPoint(x: -layerWidth/2 + 30, y: 100)
lazy var bPosition = CGPoint(x: (self.view.frame.width - layerWidth / 2) - 30 , y: 400)
override func viewDidLoad() {
super.viewDidLoad()
if circleA == nil{
circleA = CAShapeLayer()
let pa = UIBezierPath(ovalIn: CGRect(origin: aPosition, size: layerSize))
circleA.path = pa.cgPath
circleA.fillColor = UIColor.orange.cgColor
view.layer.addSublayer(circleA)
}
if circleB == nil{
circleB = CAShapeLayer()
let pa = UIBezierPath(ovalIn: CGRect(origin: bPosition, size: layerSize))
circleB.path = pa.cgPath
circleB.fillColor = UIColor.orange.cgColor
view.layer.addSublayer(circleB)
}
}
override func viewDidAppear(_ animated: Bool) {
animateCircleA()
animateCircleB()
}
fileprivate func animateCircleA(){
lazy var startPointA = CGPoint(x: -layerWidth/2 + 30, y: 0)
lazy var endPointA = CGPoint(x: (self.view.frame.width - layerWidth / 2), y: 300)
let animate = CABasicAnimation(keyPath: "position")
animate.timingFunction = CAMediaTimingFunction(name: .linear)
animate.fromValue = NSValue(cgPoint: startPointA)
animate.toValue = NSValue(cgPoint: endPointA)
animate.repeatCount = 2
animate.duration = 5.0
circleA.add(animate, forKey: "position")
}
fileprivate func animateCircleB(){
lazy var startPointB = CGPoint(x: 0, y: 0)
lazy var endPointB = CGPoint(x:-(self.view.frame.width - layerWidth / 2) - 30, y: -(self.view.frame.width - layerWidth / 2) + 30)
let animate = CABasicAnimation(keyPath: "position")
animate.timingFunction = CAMediaTimingFunction(name: .linear)
animate.fromValue = NSValue(cgPoint: startPointB)
animate.toValue = NSValue(cgPoint: endPointB)
animate.repeatCount = 2
animate.duration = 5.0
circleB.add(animate, forKey: "position")
}
}
Kindly suggest me right way to get started.
How to make both layer transparent while crossing each other?

The easiest way is to just set the alphas of the layers, but if you don't want the layers to be transparent, then that means you want a different blend mode for the layers.
According to this question, to change the blend mode of a layer, you can set CALayer.compositingFilter. From your desired output, it seems like the screen blend mode is appropriate:
circleA.compositingFilter = "screenBlendMode"
circleA.compositingFilter = "screenBlendMode"
Note that you should add your layers to a view with a transparent background, otherwise the background color of the view is also blended with the circles' colours. This view can be added as a subview of whatever view you were originally going to put the circles in, filling its entire bounds.
someView.backgroundColor = .clear
someView.layer.addSublayer(circleA)
someView.layer.addSublayer(circleB)

Related

Cloud moving animation in Swift? Is it possible?

We are following this tutorial to http://www.invasivecode.com/weblog/core-animation-scroll-layer-cascrolllayer
As per their tutorial animation will stop based on the layer bounds reached. How to do the cloud moving animation by using core animation or in Imageview?
Is it possible?
Gif or Lottie animation not needed here, we excepting through code.
Note that we have tried their code and its working but lack is there as i mentioned above.
How to keep on moving animation like cloud does?
Think twice before devoting this question.
Code:
class ViewController: UIViewController {
var translation: CGFloat = 0.0
public var currentWidth : CGFloat = {
let width = UIScreen.main.bounds.width
return width
}()
public var currentHeight : CGFloat = {
let width = UIScreen.main.bounds.height
return width
}()
lazy var scrollLayer : CAScrollLayer = {
let scrollLayer = CAScrollLayer() // 8
scrollLayer.bounds = CGRect(x: 0.0, y: 0.0, width: currentWidth, height: currentHeight) // 9
scrollLayer.position = CGPoint(x: self.view.bounds.size.width/2, y: self.view.bounds.size.height/2) // 10
scrollLayer.borderColor = UIColor.black.cgColor // 11
// scrollLayer.borderWidth = 5.0 // 12
scrollLayer.scrollMode = CAScrollLayerScrollMode.horizontally // 13
return scrollLayer
}()
lazy var displayLink: CADisplayLink = {
let displayLink = CADisplayLink(target: self, selector: #selector(scrollLayerScroll))
if #available(iOS 15.0, *) {
displayLink.preferredFrameRateRange = CAFrameRateRange(minimum: 5.0, maximum: 8.0, __preferred: 6.0)
} else {
displayLink.preferredFramesPerSecond = 5
// Fallback on earlier versions
}
return displayLink
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if let img = UIImage(named: "new") { // 1
let imageSize = img.size
let layer = CALayer() // 2
layer.bounds = CGRect(x: 0.0, y: 0.0, width: currentWidth * 50, height: currentHeight * 5) // 3
// layer.position = CGPoint(x: imageSize.width/2, y: imageSize.height/2) // 4
layer.contents = img.cgImage // 5
view.layer.addSublayer(scrollLayer) // 6
scrollLayer.addSublayer(layer) // 7
}
displayLink.add(to: RunLoop.current, forMode: .common)
}
#objc func scrollLayerScroll() {
let newPoint = CGPoint(x: translation, y: 0.0)
scrollLayer.scroll(newPoint)
translation += 10.0
// if translation > 1600.0 {
//// stopDisplayLink()
// }
}
func stopDisplayLink() {
displayLink.invalidate()
}
}
Here setting width too high for animating the layer extra time
layer.bounds = CGRect(x: 0.0, y: 0.0, width: currentWidth * 50, height: currentHeight * 5) // 3
We ended up with following solution by rob mayoff
Animate infinite scrolling of an image in a seamless loop
Please find the updated code.
class CloudPassingVC: UIViewController {
#IBOutlet weak var imageToAnimate: UIImageView!
var cloudLayer: CALayer? = nil
var cloudLayerAnimation: CABasicAnimation? = nil
override func viewDidLoad() {
super.viewDidLoad()
cloudScroll()
}
func cloudScroll() {
let cloudsImage = UIImage(named: "clouds")
let cloudPattern = UIColor(patternImage: cloudsImage!)
cloudLayer = CALayer()
cloudLayer!.backgroundColor = cloudPattern.cgColor
cloudLayer!.transform = CATransform3DMakeScale(1, -1, 1)
cloudLayer!.anchorPoint = CGPoint(x: 0, y: 1)
let viewSize = imageToAnimate.bounds.size
cloudLayer!.frame = CGRect(x: 0, y: 0, width: (cloudsImage?.size.width ?? 0.0) + viewSize.width, height: viewSize.height)
imageToAnimate.layer.addSublayer(cloudLayer!)
let startPoint = CGPoint.zero
let endPoint = CGPoint(x: -(cloudsImage?.size.width ?? 0.0), y: 0)
cloudLayerAnimation = CABasicAnimation(keyPath: "position")
cloudLayerAnimation!.timingFunction = CAMediaTimingFunction(name: .linear)
cloudLayerAnimation!.fromValue = NSValue(cgPoint: startPoint)
cloudLayerAnimation!.toValue = NSValue(cgPoint: endPoint)
cloudLayerAnimation!.repeatCount = 600
cloudLayerAnimation!.duration = 10.0
applyCloudLayerAnimation()
}
func applyCloudLayerAnimation() {
cloudLayer!.add(cloudLayerAnimation!, forKey: "position")
}
}

Group Animation in ios

I'm making a custom loader which has circular shaped layer inside and It will animate in two way. First shape will animate pulsing like scaling it's radius increase and decrease with infinite time and in same time it will rotate inside the view with around the center of view. But now my group animation does't work properly, view didn't rotate full radius and scaling didn't worked
here's my code review and tell me where I'm doing mistake.
class ViewController: UIViewController {
#IBOutlet weak var loaderView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
//circularLoader()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
drawCircularLoader()
}
func drawCircularLoader(){
let width = loaderView.bounds.width
let halfWidth = width / 2
let size = CGSize(width: halfWidth / 2, height: halfWidth / 2)
let rect = CGRect(x: halfWidth - (size.width / 2), y: (halfWidth / 2) - (size.height / 2), width: size.width, height: size.height)
let path = UIBezierPath(ovalIn: rect)
let layer = CAShapeLayer()
layer.frame = rect
layer.fillColor = UIColor.black.cgColor
layer.path = path.cgPath
loaderView.layer.addSublayer(layer)
let rotation = CABasicAnimation(keyPath: "transform.rotation")
rotation.fromValue = 0
rotation.toValue = 2.0 * .pi
rotation.duration = 1.5
layer.add(rotation, forKey: nil)
let scaling = CABasicAnimation(keyPath: "transform.scale")
scaling.toValue = 1.2
scaling.duration = 0.3
scaling.autoreverses = true
let group = CAAnimationGroup()
group.animations = [rotation, scaling]
group.repeatCount = .infinity
layer.add(group, forKey: nil)
}
}
The immediate problem is that the animation group has no duration. It needs a duration long enough for all its child animations to complete. Otherwise everything just stops after .2 seconds (the default). Try giving the group a duration of 1.5.
In a broader sense I doubt whether your overall goal is a good use of an animation group at all. But that's a different matter. Plus you have two transform animations; they might be interfering with each other (transform is one animatable property), so a different approach might be needed.

Swift Circular Movement Animation of an image [duplicate]

This question already has answers here:
UIView animation along a round path?
(3 answers)
Closed 4 years ago.
Creating an app where a an image moves upon the press of a button(repeated 3 times). The image currently has a linear movement but what I need is a circular movement animation for it.
The image should start from the left of the screen, go towards top and then to the right of the screen so there are 3 points where the image stops. This has to be done with a circular animation.
enum TimeOfDay: String {
case Dimineata = "Dimineata"
case Pranz = "Pranz"
case Seara = "Seara"
}
class ViewController: UIViewController {
#IBOutlet weak var timeOfDayLabel: UILabel!
#IBOutlet weak var sunImage: UIImageView!
var timeOfDay = TimeOfDay.Dimineata
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func didTapNext(_ sender: Any) {
switch timeOfDay {
case .Dimineata:
timeOfDay = .Pranz
sunImage.image = UIImage(named: "LunchSun")
UIView.animate(withDuration: 1) {
self.sunImage.frame = CGRect(x: self.view.center.x-50, y: 120, width: 100, height: 100)
}
case .Pranz:
timeOfDay = .Seara
sunImage.image = UIImage(named: "NightSun")
UIView.animate(withDuration: 1) {
self.sunImage.frame = CGRect(x: self.view.frame.width-50, y: 166, width: 100, height: 100)
}
default:
timeOfDay = .Dimineata
sunImage.image = UIImage(named: "NightSun")
UIView.animate(withDuration: 1) {
self.sunImage.frame = CGRect(x: self.view.frame.origin.x-50, y: 166, width: 100, height: 100)
}
}
timeOfDayLabel.text = timeOfDay.rawValue.uppercased()
}
}
Create a circular path
Create a CAKeyframeAnimation
Set the animation.path to the circular path
Add that animation to the layer of the image view
Set the frame of image view to the last point in the path
Example code:
var imageView : UIImageView!
// Adapt these constants to your case
let initialX: CGFloat = 40
let imageViewWidth: CGFloat = 60
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
imageView = UIImageView()
imageView.backgroundColor = .red
imageView.frame = CGRect(x: initialX,
y: view.frame.height / 2.0 - 20,
width: 60,
height: 40)
imageView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(imageView)
}
#IBAction func animate(_ sender: Any) {
//You can customize this path
let circularPath = UIBezierPath(arcCenter: view.center,
radius: view.frame.width / 2 - initialX - imageViewWidth / 2,
startAngle: -.pi,
endAngle: 0,
clockwise: true)
//Create the animation
let animation = CAKeyframeAnimation(keyPath: #keyPath(CALayer.position))
animation.duration = 2 // In seconds
animation.repeatCount = 1 //At maximum it could be MAXFLOAT if you want the animation to seem to loop forever
animation.path = circularPath.cgPath
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut) //Optional
//Add the animation to the layer of the image view
imageView.layer.add(animation, forKey: "myAnimation")
//prevent the imageView from going back to its original coordinates
imageView.frame.origin.x = circularPath.cgPath.currentPoint.x - imageView.frame.width / 2
imageView.frame.origin.y = circularPath.cgPath.currentPoint.y - imageView.frame.height / 2
}

Animating a circular progress circle smoothly via buttons

I am experiencing odd visual quirks with my progress circle. I would like the green circle (CAShapeLayer) to smoothly animate in increments of fifths. 0/5 = no green circle. 5/5 = full green circle. The problem is the green circle is animating past where it should be, then abruptly shrinks back to the proper spot. This happens when both the plus and minus button are pressed. There is a gif below demonstrating what is happening.
The duration of each animation should last 0.25 seconds, and should smoothly animate both UP and DOWN (depending if the plus or minus button is pressed) from wherever the animation ended last (which is currently not happening.)
In my UIView, I draw the circle, along with the method to animate the position of the progress:
class CircleView: UIView {
let progressCircle = CAShapeLayer()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override func draw(_ rect: CGRect) {
let circlePath = UIBezierPath(ovalIn: self.bounds)
progressCircle.path = circlePath.cgPath
progressCircle.strokeColor = UIColor.green.cgColor
progressCircle.fillColor = UIColor.red.cgColor
progressCircle.lineWidth = 10.0
// Add the circle to the view.
self.layer.addSublayer(progressCircle)
}
func animateCircle(circleToValue: CGFloat) {
let fifths:CGFloat = circleToValue / 5
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.duration = 0.25
animation.fromValue = fifths
animation.byValue = fifths
animation.fillMode = kCAFillModeBoth
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
progressCircle.strokeEnd = fifths
// Create the animation.
progressCircle.add(animation, forKey: "strokeEnd")
}
}
In my VC:
I init the circle and starting point with:
let myDrawnCircle = CircleView()
var startingPointForCircle = CGFloat()
viewDidAppear:
startingPointForCircle = 0.0
myDrawnCircle.frame = CGRect(x: 100, y: 100, width: 150, height: 150)
myDrawnCircle.backgroundColor = UIColor.white
myDrawnCircle.animateCircle(circleToValue: startingPointForCircle)
self.view.addSubview(myDrawnCircle)
textLabel.text = "\(startingPointForCircle)"
And the actual buttons that do the cool animating:
#IBAction func minusButtonPressed(_ sender: UIButton) {
if startingPointForCircle > 0 {
startingPointForCircle -= 1
textLabel.text = "\(startingPointForCircle)"
myDrawnCircle.animateCircle(circleToValue: startingPointForCircle)
}
}
#IBAction func plusButtonPressed(_ sender: UIButton) {
if startingPointForCircle < 5 {
startingPointForCircle += 1
textLabel.text = "\(startingPointForCircle)"
myDrawnCircle.animateCircle(circleToValue: startingPointForCircle)
}
}
Here's a gif showing you what's going on with the jerky animations.
How do I make my animations smoothly animate TO the proper value FROM the proper value?
My guess is it has something to do with using a key path. I don't know how exactly to fix it for using a key path, but you could try a different tack, using a custom view, and animating the value passed to the method used for drawing an arc.
The code below was generated by PaintCode. The value passed for arc specifies the point on the circle to draw the arc to. This is used in the drawRect method of a custom UIView. Make a CGFloat property of the custom view and simply change that value, and call setNeedsDisplay on the view after your animation code.
func drawCircleProgress(frame frame: CGRect = CGRect(x: 0, y: 0, width: 40, height: 40), arc: CGFloat = 270) {
//// General Declarations
let context = UIGraphicsGetCurrentContext()
//// Color Declarations
let white = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)
let white50 = white.colorWithAlpha(0.5)
//// Oval Drawing
CGContextSaveGState(context)
CGContextTranslateCTM(context, frame.minX + 20, frame.minY + 20)
CGContextRotateCTM(context, -90 * CGFloat(M_PI) / 180)
let ovalRect = CGRect(x: -19.5, y: -19.5, width: 39, height: 39)
let ovalPath = UIBezierPath()
ovalPath.addArcWithCenter(CGPoint(x: ovalRect.midX, y: ovalRect.midY), radius: ovalRect.width / 2, startAngle: 0 * CGFloat(M_PI)/180, endAngle: -arc * CGFloat(M_PI)/180, clockwise: true)
white.setStroke()
ovalPath.lineWidth = 1
ovalPath.stroke()
CGContextRestoreGState(context)
}
The desired results were as simple as commenting out fromValue / byValue in the animateCircle method. This was pointed out by #dfri - "set both fromValue and toValue to nil then: "Interpolates between the previous value of keyPath in the target layer’s presentation layer and the current value of keyPath in the target layer’s presentation layer."
//animation.fromValue = fifths
//animation.byValue = fifths

Add animation to UIImageView

I am trying to animate an imageView with this CAAnimation so I added the following extension to UIImageView:
extension UIImageView {
var ovalPathSmall: UIBezierPath {
return UIBezierPath(ovalInRect: CGRect(x: 50.0, y: 50.0, width: 0.0, height: 0.0))
}
var ovalPathLarge: UIBezierPath {
return UIBezierPath(ovalInRect: CGRect(x: 2.5, y: 17.5, width: 95.0, height: 95.0))
}
var ovalPathSquishVertical: UIBezierPath {
return UIBezierPath(ovalInRect: CGRect(x: 2.5, y: 20.0, width: 95.0, height: 90.0))
}
var ovalPathSquishHorizontal: UIBezierPath {
return UIBezierPath(ovalInRect: CGRect(x: 5.0, y: 20.0, width: 90.0, height: 90.0))
}
func wobble() {
let animationDuration: CFTimeInterval = 0.3
// 1
var wobbleAnimation1: CABasicAnimation = CABasicAnimation(keyPath: "path")
wobbleAnimation1.fromValue = ovalPathLarge.CGPath
wobbleAnimation1.toValue = ovalPathSquishVertical.CGPath
wobbleAnimation1.beginTime = 0.0
wobbleAnimation1.duration = animationDuration
// 2
var wobbleAnimation2: CABasicAnimation = CABasicAnimation(keyPath: "path")
wobbleAnimation2.fromValue = ovalPathSquishVertical.CGPath
wobbleAnimation2.toValue = ovalPathSquishHorizontal.CGPath
wobbleAnimation2.beginTime = wobbleAnimation1.beginTime + wobbleAnimation1.duration
wobbleAnimation2.duration = animationDuration
// 3
var wobbleAnimation3: CABasicAnimation = CABasicAnimation(keyPath: "path")
wobbleAnimation3.fromValue = ovalPathSquishHorizontal.CGPath
wobbleAnimation3.toValue = ovalPathSquishVertical.CGPath
wobbleAnimation3.beginTime = wobbleAnimation2.beginTime + wobbleAnimation2.duration
wobbleAnimation3.duration = animationDuration
// 4
var wobbleAnimation4: CABasicAnimation = CABasicAnimation(keyPath: "path")
wobbleAnimation4.fromValue = ovalPathSquishVertical.CGPath
wobbleAnimation4.toValue = ovalPathLarge.CGPath
wobbleAnimation4.beginTime = wobbleAnimation3.beginTime + wobbleAnimation3.duration
wobbleAnimation4.duration = animationDuration
// 5
var wobbleAnimationGroup: CAAnimationGroup = CAAnimationGroup()
wobbleAnimationGroup.animations = [wobbleAnimation1, wobbleAnimation2, wobbleAnimation3,
wobbleAnimation4]
wobbleAnimationGroup.duration = wobbleAnimation4.beginTime + wobbleAnimation4.duration
wobbleAnimationGroup.repeatCount = 2
self.layer.addAnimation(wobbleAnimationGroup, forKey: nil)
}
then this to my viewDiLoad:
NSTimer.scheduledTimerWithTimeInterval(0.3, target: self, selector: "x",
userInfo: nil, repeats: false)
func x() {
questionIcon.wobble()
}
questionIcon is an outlet to the image I want to animate, but it doesn't move! What is wrong with the code? thanks
It isn't at all clear what you are trying to do; your code as it stands is nonsense. You are making animation objects like this:
CABasicAnimation(keyPath: "path")
But a UIImageView does not have a path property so you are not animating anything. The whole notion that a UIImageView might have an animatable path property is just something you made up in your head. You don't get to do that...
What you are allowed to do with a UIImageView is prepare a sequence of images (which you can create in code) and hand them to the image view to be played in sequence (as its animationImages) like the frames of a cartoon. Perhaps that is the direction you should be taking here.

Resources