I have the following code:
private var bounceAnimation: CAKeyframeAnimation = {
let bounceAnimation = CAKeyframeAnimation(keyPath: "transform.scale")
bounceAnimation.values = [1.0, 1.4, 0.9, 1.02, 1.0]
bounceAnimation.duration = TimeInterval(0.3)
bounceAnimation.calculationMode = CAAnimationCalculationMode.cubic
return bounceAnimation
}()
This creates the animation where the icon gets bigger and then smaller. I am trying to create the animation where the icon gets smaller and then back to normal like it's being pushed similar to twitter, Spotify, etc. I assume it's just changing around the bounce values all though I'm not sure how would I do this.
I'd use a normal UIView.animate function like this:
UIView.animate(withDuration: 0.05, delay: 0, options: .curveLinear, animations: {
view.transform = CGAffineTransform(scaleX: 1.05, y: 1.05)
}, completion: nil)
UIView.animate(withDuration: 0.3, delay: 0.05, usingSpringWithDamping: 0.2, initialSpringVelocity: 7, options: .curveEaseOut, animations: {
view.transform = .identity
}, completion: nil)
Just change view to be whatever view you're trying to animate. Then mess around with the initial scale, the duration and the spring dampening to get the animation you want!
Related
I'm having an issue trying to touch a label that keeps animating because during the animation it becomes an image (set the animation on repeat). Is there a way to touch it during animation (I cant't use UITapGestureRecognizer) ?
func animatePro() {
UIView.animate(withDuration: 2, delay: 0, options: [.autoreverse, .repeat], animations: {
//1.5 times it's normal size
self.proLabel.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
}){ (finished) in
self.proLabel
.transform = CGAffineTransform.identity
}
}
func animateRev() {
UIView.animate(withDuration: 2, delay: 0, options: [.repeat], animations: {
//1.5 times it's normal size
self.revLabel.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
}){ (finished) in
UIView.animate(withDuration: 2, delay: 0, animations: {
self.revLabel.transform = CGAffineTransform.identity
})
}
}
Add allowUserInteraction
options: [.allowUserInteraction,.autoreverse, .repeat]
I need make animation to show/hide view (UIButton), so alpha changes from 0 to 1 same time as scale of view changes from 0 to 100% (showing) and reverse (hiding). Each animation I can do separately but how make it together? And It must showing/hiding correct when user randomly (and may be so fast, for example, hiding animation not finished - showing animation starts) interact. Any help will be appreciated.
continuing the previous comments, I try to make a simple example.
so maybe the trick is in the transform "identity" which functions to restore the view to its origin, hopefully it helps
#IBAction func showButtonTapped(_ sender: Any) {
UIView.animate(withDuration: 1.0, delay: 0.5, usingSpringWithDamping: 0.6, initialSpringVelocity: 1, options: [.curveEaseIn], animations: {
self.yourView.transform = .identity
self.yourView.alpha = 1
}, completion: nil)
}
#IBAction func hideButtonTapped(_ sender: Any) {
UIView.animate(withDuration: 1.0, delay: 0.5, usingSpringWithDamping: 0.6, initialSpringVelocity: 1, options: [.curveEaseOut], animations: {
self.yourView.transform = CGAffineTransform.identity.scaledBy(x: 0.7, y: 0.7)
self.yourView.alpha = 0
})
}
I've added this animation code to the end of a function that is called when the button is pushed, but while the animation is happening, it ignores the set target selector until the animation is finished. The animation is very fast, but I'd like users to be able to press it rapidly.
let transforms: CGAffineTransform = .identity
mirroredButton.transform = transforms
UIView.animate(withDuration: 0.05, animations: {
mirroredButton.transform = transforms.scaledBy(x: 0.75, y: 0.75)
},
completion: { _ in
UIView.animate(withDuration: 0.1) {
mirroredButton.transform = transforms.scaledBy(x: 1.0, y: 1.0)
}
})
Update:
Using the answer, I updated my animation code as shown below. Both animation calls need options. The 2nd has a nil completion handler.
let transforms: CGAffineTransform = .identity
mirroredButton.transform = transforms
UIView.animate(withDuration: 0.05, delay: 0.0, options: .allowUserInteraction, animations: {
mirroredButton.transform = transforms.scaledBy(x: 0.75, y: 0.75)
},
completion: { _ in
UIView.animate(withDuration: 0.1, delay: 0.0, options: .allowUserInteraction, animations: {
mirroredButton.transform = transforms.scaledBy(x: 1.0, y: 1.0)
}, completion:nil)
})
User interaction is disabled for the duration of a view's animation. If it's critical that the user should interact with a view during an animation, you can pass in the option .allowUserInteraction, like so:
UIView.animate(withDuration: 1.0, delay: 0.0, options: .allowUserInteraction, animations: {
//animate here
})
I would like to animate the scale of a UIButton, and animate the scale again when completed, but have it rotate without animation. I've tried to put the rotation transform in an animation call with no duration, but somehow it still makes it part of or replace the scaling animation.
I extended the animation of the scale by a second to demonstrate the result more clearly.
let transforms: CGAffineTransform = .identity
button.transform = transforms
UIView.animate(withDuration: 1.05, delay: 0.0, options: .allowUserInteraction, animations: {
button.transform = transforms.scaledBy(x: 0.75, y: 0.75)
}, completion: { _ in
button.transform = CGAffineTransform(rotationAngle: 90 * (.pi/180)) // I want this to happen immediately, without animation
UIView.animate(withDuration: 1.1, delay: 0.0, options: .allowUserInteraction, animations: {
button.transform = transforms.scaledBy(x: 1.0, y: 1.0)
}, completion:nil)
})
You set the rotate transform to happen immediately - and it does! But then you immediately start animating it again, with respect to your transforms variable - reverting its current transform to the one in that variable.
Instead of using a variable to keep track of the button's transform, just refer to its current transform, like so:
button.transform = .identity
UIView.animate(withDuration: 1.05, delay: 0.0, options: .allowUserInteraction, animations: {
button.transform = button.transform.scaledBy(x: 0.75, y: 0.75)
}, completion: { _ in
button.transform = CGAffineTransform(rotationAngle: 90 * (.pi/180)) // I want this to happen immediately, without animation
UIView.animate(withDuration: 1.1, delay: 0.0, options: .allowUserInteraction, animations: {
button.transform = button.transform.scaledBy(x: 1.0, y: 1.0)
}, completion:nil)
})
I have a view controller that has a UIView that I call playerView. In playerView, I use an AVLayer and AVPlayerLayer to show the user a video. Image the Youtube app when you first click on any video.
How do I manipulate the frame of this playerView so that it can take up the entire screen. (Go full screen)
This is the current frame:
//16 x 9 is the aspect ratio of all HD videos
let height = view.frame.width * 9 / 16
let playerFrame = CGRect(x: 0, y: 0, width: view.frame.width, height: height)
playerView.frame = videoPlayerFrame
I was testing around in the YouTube app and their app only supports Portrait orientation (just like mine) due to how the animation of a video going full screen looks. How can I do this at the the tap of a button and even further when the user holds the device horizontally?
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.playerView.frame = ?
}, completion: nil)
Edit: Tried this but it doesn't really work how I want it... it doesn't take up the whole screen
UIView.animate(withDuration: 0.8, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.playerView.frame = CGRect(x: 0, y: 0, width: self.view.frame.height, height: self.view.frame.width)
self.playerView.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi / 2))
}, completion: nil)
I figured it out.
I create a global variable called isExpanded that will be set to true when we're in full screen mode. Also, I create a CGPoint variable called videoPlayerViewCenter so I can save the center before going full screen so I can set it again when going back to small screen.
This is the code that gets called when the user wants to go full screen
func fullScreenTapped(sender: vVideoPlayer) {
if !isExpanded {
//Expand the video
UIView.animate(withDuration: 0.8, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.videoPlayerViewCenter = self.videoPlayerView.center
self.videoPlayerView.frame = CGRect(x: 0, y: 0, width: self.view.frame.height, height: self.view.frame.width)
self.videoPlayerView.center = self.view.center
self.videoPlayerView.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi / 2))
self.videoPlayerView.layoutSubviews()
}, completion: nil)
} else {
//Shrink the video again
UIView.animate(withDuration: 0.8, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
//16 x 9 is the aspect ratio of all HD videos
self.videoPlayerView.transform = CGAffineTransform.identity
self.videoPlayerView.center = self.videoPlayerViewCenter
let height = self.view.frame.width * 9 / 16
let videoPlayerFrame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: height)
self.videoPlayerView.frame = videoPlayerFrame
self.videoPlayerView.layoutSubviews()
}, completion: nil)
}
isExpanded = !isExpanded
}
To expand the video, I save the center in my global CGPoint variable and then set the frame. Then, I set the center and do a 90 degree turn using CGAffineTransform.
To shrink the video again, I basically set the settings to how they were before. (The order in which things are done is very important)
Another thing that's also very important is to override your layoutSubviews method in your videoPlayerView to be like this:
override func layoutSubviews() {
super.layoutSubviews()
self.playerLayer?.frame = self.bounds
}
This will make it so your playerLayer adjusts its frame as the view is getting bigger.
I know is too late but maybe someone else can find this code helpful.
I use to use Auto layout constraints in my views so animating must have a different approach.
func setMapFullScreen(_ fullscreen: Bool) {
if fullscreen {
// Set the full screen constraints
self.setFullScreenLayoutConstraints()
// Hide the navigation bar
self.navigationController?.setNavigationBarHidden(true, animated: true)
} else {
// Set small screen constraints
self.setSmallScreenLayoutConstraints()
// Show the navigation bar
self.navigationController?.setNavigationBarHidden(false, animated: true)
}
// Animate to full screen
UIView.animate(withDuration: 0.8, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.view.layoutIfNeeded()
}) { (finished) in
// ...
}
In my case a have I MKMapView and I want to tap on it in order to see the map fullscreen (and back).
This is the function that toogle the state.
As you can see, in the UIView.animate call, I animate only the self.view.layoutIfNeeded()
I also hide the navigation bar cause I have one ☺
Here my setSmallScreenLayoutConstraints and setFullScreenLayoutConstraints functions using SnapKit framework
func setSmallScreenLayoutConstraints() {
self.mapView.snp.remakeConstraints { (make) -> Void in
make.top.equalTo(self.mapLabel.snp.bottom).offset(8)
make.left.equalTo(self.view).offset(0)
make.right.equalTo(self.view).offset(0)
make.height.equalTo(150)
}
}
func setFullScreenLayoutConstraints() {
self.mapView.snp.remakeConstraints { (make) -> Void in
make.top.equalTo(self.view).offset(0)
make.left.equalTo(self.view).offset(0)
make.right.equalTo(self.view).offset(0)
make.bottom.equalTo(self.view).offset(0)
}
}
This is the result
Enjoy!!
As I said it is not an answer for your question. It is an idea and hope it helps you:
var rectangleView:UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Tap Gesture Recognizer for growing the rectangleView
let tapForward = UITapGestureRecognizer.init(target: self, action: #selector(self.tapForward(tap:)))
tapForward.numberOfTapsRequired = 1
// Configure the rectangleView
self.rectangleView = UIView.init(frame: CGRect.init(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height))
self.rectangleView.transform = CGAffineTransform.init(scaleX: 0.65, y: 0.25)
self.rectangleView.backgroundColor = UIColor.red
self.rectangleView.addGestureRecognizer(tapForward)
self.view.addSubview(rectangleView)
}
And this going to be our tapForward(tap:) function:
func tapForward(tap:UITapGestureRecognizer) {
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.8, options: .curveEaseInOut, animations: {
self.rectangleView.transform = CGAffineTransform.identity
}) { (completed:Bool) in
// Completion block
}
}