Add animation to UIImageView - ios

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.

Related

merge two CAShaplayer with transparent in Swift5?

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)

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")
}
}

UIImageView is not animating

I am trying to have a view in which 3 dots would animate infinity and here is the code snippet(swift 5) for it
func showAnimatingDotsInImageView() {
let lay = CAReplicatorLayer()
lay.frame = CGRect(x: 0, y: 0, width: 15, height: 7) //yPos == 12
let circle = CALayer()
circle.frame = CGRect(x: 0, y: 0, width: 7, height: 7)
circle.cornerRadius = circle.frame.width / 2
circle.backgroundColor = UIColor.green.cgColor
lay.addSublayer(circle)
lay.instanceCount = 3
lay.instanceTransform = CATransform3DMakeTranslation(10, 0, 0)
let anim = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
anim.fromValue = 1.0
anim.toValue = 0.2
anim.duration = 1
anim.repeatCount = .infinity
circle.add(anim, forKey: nil)
lay.instanceDelay = anim.duration / Double(lay.instanceCount)
test1.layer.addSublayer(lay)
}
In the above code test1 is a UIImageView.
The main issue is the dots are shown but it's static and doesn't animate.
I tried your code and it is animating just fine.
Any chance you are calling this from a thread which is different than the Main thread? Like for example something on the background is happening, which you want to triggers the loading?
Try adding:
DispatchQueue.main.async { [weak self] in
self?.showAnimatingDotsInImageView()
}

Swift - CABasicAnimation not working with CALayer

Here is my code about add a layer with animation added.
let myLayer: CALayer = .init()
let myAnimation: CABasicAnimation = .init(keyPath: "bounds.size.height")
// for myLayer to center of superview
let centerPoint: CGPoint = .init(x: mySuperview.bounds.width / 2, y: mySuperview.bounds.height / 2)
myLayer.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
myLayer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
myLayer.position = centerPoint
myLayer.backgroundColor = UIColor.red.cgColor
myAnimation.fromValue = 20
myAnimation.toValue = 100
myAnimation.duration = 1
myAnimation.beginTime = CACurrentMediaTime() + 1.0
myAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
myLayer.add(myAnimation, forKey: "bounds.size.height")
mySuperview.layer.addSublayer(myLayer)
and myLayer was appeared at correct position. but added animation isn't working.
not only about bounds.size.height. frame.size.height, transform, opacity... I tried all of them but It isn't changing.
I am confused because I have a recollection of working well easily by above code.
Did I skip anything?

CABasicAnimation runs after Implicit Animation

I want a layer to behave like this:
Instead, it behaves like this:
The card flip animation is created by two CABasicAnimations applied in a CAAnimationGroup. The incorrect spin effect happens because the implicit animation from the CALayer property change runs first and then my animation specified in the CABasicAnimation runs. How can I stop the implicit animation from running so that only my specified animation runs?
Here's the relevant code:
class ViewController: UIViewController {
var simpleLayer = CALayer()
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
self.view.addGestureRecognizer(tap)
simpleLayer.frame = CGRect(origin: CGPoint(x: view.bounds.width / 2 - 50, y: view.bounds.height / 2 - 50), size: CGSize(width: 100, height: 100))
simpleLayer.backgroundColor = UIColor.blackColor().CGColor
view.layer.addSublayer(simpleLayer)
}
func handleTap() {
let xRotation = CABasicAnimation(keyPath: "transform.rotation.x")
xRotation.toValue = 0
xRotation.byValue = M_PI
let yRotation = CABasicAnimation(keyPath: "transform.rotation.y")
yRotation.toValue = 0
yRotation.byValue = M_PI
simpleLayer.setValue(M_PI, forKeyPath: "transform.rotation.y")
simpleLayer.setValue(M_PI, forKeyPath: "transform.rotation.x")
let group = CAAnimationGroup()
group.animations = [xRotation, yRotation]
group.duration = 0.6
group.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
simpleLayer.addAnimation(group, forKey: nil)
}
}
#LucasTizma had the correct answer.
Surround your animation with CATransaction.begin(); CATransaction.setDisableActions(true) and CATransaction.commit(). This will disable the implicit animation and make the CAAnimationGroup animate correctly.
Here's the final result:
This is the important snippet of code in Swift 3:
CATransaction.begin()
CATransaction.setDisableActions(true)
let xRotation = CABasicAnimation(keyPath: "transform.rotation.x")
xRotation.toValue = 0
xRotation.byValue = M_PI
let yRotation = CABasicAnimation(keyPath: "transform.rotation.y")
yRotation.toValue = 0
yRotation.byValue = M_PI
simpleLayer.setValue(M_PI, forKeyPath: "transform.rotation.x")
simpleLayer.setValue(M_PI, forKeyPath: "transform.rotation.y")
let group = CAAnimationGroup()
group.animations = [xRotation, yRotation]
simpleLayer.add(group, forKey: nil)
CATransaction.commit()
And this is the full code for the depicted animation with an iOS app:
class ViewController: UIViewController {
var simpleLayer = CALayer()
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
self.view.addGestureRecognizer(tap)
let ratio: CGFloat = 1 / 5
let viewWidth = view.bounds.width
let viewHeight = view.bounds.height
let layerWidth = viewWidth * ratio
let layerHeight = viewHeight * ratio
let rect = CGRect(origin: CGPoint(x: viewWidth / 2 - layerWidth / 2,
y: viewHeight / 2 - layerHeight / 2),
size: CGSize(width: layerWidth, height: layerHeight))
let topRightPoint = CGPoint(x: rect.width, y: 0)
let bottomRightPoint = CGPoint(x: rect.width, y: rect.height)
let topLeftPoint = CGPoint(x: 0, y: 0)
let linePath = UIBezierPath()
linePath.move(to: topLeftPoint)
linePath.addLine(to: topRightPoint)
linePath.addLine(to: bottomRightPoint)
linePath.addLine(to: topLeftPoint)
let maskLayer = CAShapeLayer()
maskLayer.path = linePath.cgPath
simpleLayer.frame = rect
simpleLayer.backgroundColor = UIColor.black.cgColor
simpleLayer.mask = maskLayer
// Smooth antialiasing
// * Convert the layer to a simple bitmap that's stored in memory
// * Saves CPU cycles during complex animations
// * Rasterization is set to happen during the animation and is disabled afterwards
simpleLayer.rasterizationScale = UIScreen.main.scale
view.layer.addSublayer(simpleLayer)
}
func handleTap() {
CATransaction.begin()
CATransaction.setDisableActions(true)
CATransaction.setCompletionBlock({
self.simpleLayer.shouldRasterize = false
})
simpleLayer.shouldRasterize = true
let xRotation = CABasicAnimation(keyPath: "transform.rotation.x")
xRotation.toValue = 0
xRotation.byValue = M_PI
let yRotation = CABasicAnimation(keyPath: "transform.rotation.y")
yRotation.toValue = 0
yRotation.byValue = M_PI
simpleLayer.setValue(M_PI, forKeyPath: "transform.rotation.x")
simpleLayer.setValue(M_PI, forKeyPath: "transform.rotation.y")
let group = CAAnimationGroup()
group.animations = [xRotation, yRotation]
group.duration = 1.2
group.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
simpleLayer.add(group, forKey: nil)
CATransaction.commit()
}
}
You are creating 2 separate animations and applying them in an animation group. When you do that they are applied as 2 discrete steps.
It looks like that's not what you want. If not, then don't create 2 separate animations, one on transform.rotation.x and the other on transform.rotation.y. Instead, concatenate both changes onto a transformation matrix and apply the changed transformation matrix as a single animation.

Resources