I am new to swift and this is my first question ever ....
I would like to shrink a ball with duration 2 seconds, and then grow it for a duration of 5 seconds.
My problem is that the second duration is ignored (ball shrinks for 2 seconds and grows for 2 seconds).
I hope someone can help me.
This is my attempt:
let ball = UIView()
ball.frame = CGRectMake(50, 50, 50, 50)
ball.backgroundColor = UIColor.blueColor()
ball.layer.cornerRadius=25
relaxContainer.addSubview(ball)
UIView.animateWithDuration(2.0, delay:0, options: [.Repeat, .Autoreverse], animations: {
ball.frame = CGRectMake(50, 50, 20, 20)
}, completion: { finished in
UIView.animateWithDuration(5.0, animations: {
ball.frame = CGRectMake(50, 50, 50, 50)
})
})
My Answer thanks to help by Matt (duration times, variables from original question were changed):
Swift 2
let duration = 6.0
let delay = 0.0
UIView.animateKeyframesWithDuration(duration, delay: delay, options: [.Repeat], animations: {
UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 1/3, animations: {
ball.frame = CGRectMake(screenWidth/8*3, screenHeight/8*3, screenWidth/4, screenWidth/4)
})
UIView.addKeyframeWithRelativeStartTime(1/3, relativeDuration: 2/3, animations: {
ball.frame = CGRectMake(screenWidth/4, screenHeight/4, screenWidth/2, screenWidth/2)
})
}, completion: nil
)
Swift 3, 4, 5
let duration = 6.0
let delay = 0.0
UIView.animateKeyframes(withDuration: duration, delay: delay, options: [.repeat], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1/3, animations: {
ball.frame = CGRect(x: screenWidth/8*3, y: screenHeight/8*3, width: screenWidth/4, height: screenWidth/4)
})
UIView.addKeyframe(withRelativeStartTime: 1/3, relativeDuration: 2/3, animations: {
ball.frame = CGRect(x: screenWidth/4, y: screenHeight/4, width: screenWidth/2, height: screenWidth/2)
})
}, completion: nil
)
Related
Is there a way to remove the delay on start and end of the animateKeyframes animation?
As you can see there is a slight delay before the animation starts; after tapping on the Animate button and also at the end of the animation. What I would like to be able to do is start the animation as soon as the Animate button is tapped since this is meant to provide feedback to the user.
Is this the normal behavior when using animateKeyframes animations with the calculationModeCubic? Is there a way to make the animation starts as soon as the button is tapped?
Sorry about the misspelling error (Aniamate).
Here is the code:
#IBAction func startAnimation(_ sender: Any) {
addMyView()
UIView.animateKeyframes(withDuration: 3.0, delay: 0.0, options: [.calculationModeCubic], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.2, animations: {
self.myView!.center = CGPoint(x: self.pointA.center.x, y: self.pointA.center.y)
})
UIView.addKeyframe(withRelativeStartTime: 0.2, relativeDuration: 0.2, animations: {
self.myView!.center = CGPoint(x: self.pointB.center.x + 55, y: self.pointB.center.y - 5 )
self.myView!.transform = CGAffineTransform(scaleX: 0.75, y: 0.75)
})
UIView.addKeyframe(withRelativeStartTime: 0.4, relativeDuration: 0.2, animations: {
self.myView!.center = CGPoint(x: self.pointB.center.x, y: self.pointB.center.y)
self.myView!.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
})
}, completion: { _ in
self.myView?.removeFromSuperview()
})
}
func addMyView(){
myView = UIView(frame: CGRect(x: pointA.center.x - 25, y: pointA.center.y - 25, width: 50, height: 50))
myView?.backgroundColor = .blue
myView?.layer.cornerRadius = myView!.frame.height / 2
view.addSubview(myView!)
}
The key is to keep tweaking the withRelativeStartTime: and the relativeDuration: parameters.
#IBAction func startAnimation(_ sender: Any) {
addMyView()
UIView.animateKeyframes(withDuration: 1.5, delay: 0.0, options: [.calculationModeCubic], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.2, animations: {
self.myView!.center = CGPoint(x: self.pointA.center.x, y: self.pointA.center.y)
})
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.9, animations: {
self.myView!.center = CGPoint(x: self.pointB.center.x + 75, y: self.pointB.center.y - 50 )
self.myView!.transform = CGAffineTransform(scaleX: 0.75, y: 0.75)
})
UIView.addKeyframe(withRelativeStartTime: 0.1, relativeDuration: 0.7, animations: {
self.myView!.center = CGPoint(x: self.pointB.center.x, y: self.pointB.center.y)
self.myView!.transform = CGAffineTransform(scaleX: 0.3, y: 0.3)
})
}, completion: { _ in
self.myView?.removeFromSuperview()
})
}
I added spring option to an UIImage. It worked fine on my phone, very nice. Bouncy and smooth.
But when I run it on the iOS simulators, it always leaves some edges on the screen.
It becomes a problem when I need to upload screenshots to App Store.
This is my code:
let theTile = numberTile
let bounds = theTile.bounds
UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 0.2, initialSpringVelocity: 10, options: [.curveEaseInOut, .allowUserInteraction], animations: {
theTile.bounds = CGRect(x: bounds.origin.x, y: bounds.origin.y, width: bounds.size.width * 1.2, height: bounds.size.height * 1.2)
}, completion: nil)
This is the shrink back code
let theTile = numberTile
let bounds = theTile.bounds
UIView.animate(withDuration: 0.3, delay: 0, options: .allowUserInteraction, animations: {
theTile.bounds = CGRect(origin: bounds.origin, size: self.size)
}, completion: nil)
Try this although this might not be your only issue. If that is a collection view I would need to see your cell for row function as you may be loading a cell with the bounds messed up to start with. Here is a suggestion though. Animate the transform instead.
//to animate larger
UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 0.2,initialSpringVelocity: 10, options: [.curveEaseInOut, .allowUserInteraction], animations: {
theTile.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
}, completion: nil)
//animate smaller
UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 0.2, initialSpringVelocity: 10, options: [.curveEaseInOut, .allowUserInteraction], animations: {
theTile.transform = .identity
}, completion: nil)
If this is a collection view in cell for row you may need to check for selected index but def if not selected you need to set the transform to identity.
EDIT:
You say this does not fix the issue. What environment are you running? Type of Mac. I personally believe it is a code issue somewhere else as I have written tons of animation code and never seen this bug. Here is a minimum example that does not create "ghost layers" on my device or simulator. If you can make a minimal example that does it would help a lot... Until then I think it is something in your code possibly your collection view.
import UIKit
class ViewController: UIViewController {
lazy var box : UIView = {
let v = UIView(frame: CGRect(x: 0, y: 0, width: 80, height: 80))
v.backgroundColor = .blue
v.center = self.view.center
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .lightGray
self.view.addSubview(box)
box.layer.cornerRadius = 8
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
fireEvent()
super.touchesBegan(touches, with: event)
}
func fireEvent(){
if box.transform == .identity{
//to animate larger
UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 0.2,initialSpringVelocity: 10, options: [.curveEaseInOut, .allowUserInteraction], animations: {
self.box.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
}, completion: nil)
}else{
//animate smaller
UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 0.2, initialSpringVelocity: 10, options: [.curveEaseInOut, .allowUserInteraction], animations: {
self.box.transform = .identity
}, completion: nil)
}
}
}
I'm either being stupid, or misunderstanding how keyframe animations work on iOS (or both!). The two animation blocks below produce different results but I would expect them to be the same:
let duration: TimeInterval = 2
UIView.animateKeyframes(withDuration: duration, delay: 0, animations: {
UIView.addKeyframe(withRelativeStartTime: 0.9, relativeDuration: 0.1, animations: {
self.someView.transform = CGAffineTransform(translationX: 0, y: 150)
})
})
UIView.animateKeyframes(withDuration: duration * 0.1, delay: duration * 0.9, animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1, animations: {
self.someView.transform = CGAffineTransform(translationX: 0, y: 150)
})
})
Can anyone help me understand why these are different when executed? The first seems to do what I expect, but the second one seems to execute the animation earlier than expected.
You're seeing the effects of the default timing curve on your keyframe animations. The first animation is the last 0.2 seconds of a 2 second animation, and the second animation is the entirety of a 0.2 second animation. The default ease-in-ease-out means that the first animation will be done entirely in the ease-out portion of the curve.
To force a linear curve on both animations you can wrap them inside another animation and set a couple of options:
UIView.animate(withDuration: duration, delay: 0, options: [.curveLinear], animations: {
UIView.animateKeyframes(withDuration: duration, delay: 0, options: [.overrideInheritedDuration, .calculationModeLinear], animations: {
UIView.addKeyframe(withRelativeStartTime: 0.9, relativeDuration: 0.1, animations: {
view1.transform = CGAffineTransform(translationX: 0, y: 150)
})
})
UIView.animateKeyframes(withDuration: duration * 0.1, delay: duration * 0.9, options: [.overrideInheritedDuration, .calculationModeLinear], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1, animations: {
view2.transform = CGAffineTransform(translationX: 0, y: 150)
})
})
}, completion: nil)
Which I think you'll agree is horrible-looking code, but I'm assuming this is an intellectual exercise :)
I'm totally newbies with iOS Swift developement and i try to combine three parameters in a single animations but i don't succeed.
I think the solution is here -Apple Dev Core Animation Programming Guide by grouping the animations but being a beginner and after a lot of Internet research i can't find what i'm looking for.
What do you think of my code and what is for you the best solution to combine performance and stability.
I want to point out that the purpose of this animation is to create an animated Splashscreen. There are other elements (UIImage) that will be to animates.
Here is my code:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
logoImg.alpha = 0
logoImg.transform = CGAffineTransform(translationX: 0, y: -200)
logoImg.transform = CGAffineTransform(scaleX: 0, y: 0)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 1.0, delay: 1.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 10, options: [.curveEaseOut], animations: {
self.logoImg.transform = CGAffineTransform(translationX: 0, y: 0)
self.logoImg.transform = CGAffineTransform(scaleX: 1, y: 1)
self.logoImg.alpha = 1
}, completion: nil)
}
Based on what I am seeing you are wanting to preset the animation and translate it back. In that case I would do this.
self.logoImg.transform = CGAffineTransform(translationX: 0, y: -200).concatenating(CGAffineTransform(scaleX: 0, y: 0))
self.logoImg.alpha = 0
UIView.animate(withDuration: 1.0, delay: 1.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 10, options: [.curveEaseOut], animations: {
self.logoImg.transform = .identity
self.logoImg.alpha = 1
}, completion: nil)
I think you may not be seeing all the animation so try to start the scale at 0.5
self.logoImg.transform = CGAffineTransform(translationX: 0, y: -200).concatenating(CGAffineTransform(scaleX: 0.5, y: 0.5))
self.logoImg.alpha = 0
UIView.animate(withDuration: 1.0, delay: 1.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 1, options: [.curveEaseOut], animations: {
self.logoImg.transform = .identity
self.logoImg.alpha = 1
}, completion: nil)
The key here is that the animation is animating back the original identity. Hope this helps
You can use concatenating method to combining two existing affine transforms.
UIView.animate(withDuration: 1.0, delay: 1.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 10, options: [.curveEaseOut], animations: {
let translation = CGAffineTransform(translationX: 0, y: 0)
let scale = CGAffineTransform(scaleX: 1, y: 1)
self.logoImg.transform = translation.concatenating(scale)
self.logoImg.alpha = 1
}, completion: nil)
Look at Apple Document for more info. Hope it help. :)
Rotate and Translate and make it like parallax effect in tableview header:
func setupTableHeaderView() {
self.customHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: 200))
self.customHeaderView?.backgroundColor = .white
self.customImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: 200))
self.customImageView?.image = ImageNamed(name: "camera")
self.customImageView?.contentMode = .center
self.customHeaderView?.addSubview(self.customImageView!)
self.tableHeaderView = self.customHeaderView
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let yPos = scrollView.contentOffset.y
if yPos < 0 {
let scaleX = ((yPos * -1) / 200) + 1
let translateY = yPos / 2
var commonTransform = CGAffineTransform.identity
commonTransform = commonTransform.translatedBy(x: 0, y: translateY)
commonTransform = commonTransform.scaledBy(x: scaleX, y: scaleX)
self.customImageView?.transform = commonTransform
}else{
self.customImageView?.transform = CGAffineTransform.identity
}
}
NOTE : Make sure the View you apply transform to is SUBVIEW of the header view, not header view itself. In above example customImageView is subview of main headerview.
You can use this also with swift 3.0.1:
UIView.transition(with: self.imageView,
duration:0.5,
options: .transitionCrossDissolve,
animations: { self.imageView.image = newImage },
completion: nil)
Reference: https://gist.github.com/licvido/bc22343cacfa3a8ccf88
I'm trying to animate labels on view. I want to show label 1, then label 2, then remove label2. But label2 is always removed. If I delete the last keyframe block (which "removes" label2), it shows properly).
Here's the code:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let pos1 = self.label1.frame
let pos2 = self.label2.frame
self.label1.frame = CGRect(x: self.view.center.x, y: -10, width: 0, height: 0)
self.label2.frame = CGRect(x: pos2.origin.x, y: self.view.frame.height+10, width: 0, height: 0)
UIView.animateKeyframesWithDuration(1.5, delay: 0.0, options: nil, animations: {
UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration: 0.4, animations: {
self.label1.frame = pos1
})
UIView.addKeyframeWithRelativeStartTime(0.4, relativeDuration: 0.4, animations: {
self.label2.frame = pos2
})
UIView.addKeyframeWithRelativeStartTime(1.0, relativeDuration: 0.5, animations: {
self.label1.frame = CGRect(x: pos2.origin.x, y: self.view.frame.height + 10, width: 0, height: 0)
})
}, completion: nil)
}
From the reference, the timing parameters in addKeyframeWithRelativeStartTime... are values in the range of 0..1 which are interpreted relative to the entire duration of the animation and NOT a value in seconds. So specifying a keyframe with a start time of 1.0 says to start the animation at the END of the entire animation. Change the start times to 0.0 / 1.5, 0.5 / 1.5, 1.0 / 1.5 and the durations to 0.4 / 1.5 and 0.5 / 1.5.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let pos1 = self.label1.frame
let pos2 = self.label2.frame
self.label1.frame = CGRect(x: self.view.center.x, y: -10, width: 0, height: 0)
self.label2.frame = CGRect(x: pos2.origin.x, y: self.view.frame.height + 10, width: 0, height: 0)
UIView.animateKeyframes(withDuration: 1.5, delay: 0.0, options: [], animations: {
UIView.addKeyframe(withRelativeStartTime: 0.0 / 1.5, relativeDuration: 0.4 / 1.5, animations: {
self.label1.frame = pos1
})
UIView.addKeyframe(withRelativeStartTime: 0.4 / 1.5, relativeDuration: 0.4 / 1.5, animations: {
self.label2.frame = pos2
})
UIView.addKeyframe(withRelativeStartTime: 1.0 / 1.5, relativeDuration: 0.5 / 1.5, animations: {
self.label1.frame = CGRect(x: pos2.origin.x, y: self.view.frame.height + 10, width: pos1.size.width, height: pos1.size.height)
})
}, completion: nil)
}
It seems there are also issues animating the size of views. Only the final size is used (note that in this case you should see the labels start from 0x0 and grow to their final size, but they don't) So setting the size in the final frame to 0x0 makes label1 have that size through the whole animation. Change the code to maintain the size and it seems to work fine. If you really want to animate the size to zero you'll probably need to animate the transform property through scaling.