Animating UIButton to zoom in and out on click - ios

I got a button that I've replaced with an icon and when the icon is clicke I want it to zoom in and out for lets say 5 seconds. How can I accomplish this? I have made a set of 5 images with different sizes for the button, can I loop thru theese or is there an other way?
#IBAction func myButton(sender: UIButton){
//animation that zoom the button icon in and out
}
Edit: Im using Xcode 6.4

To show an alternative, I will show an approach with animating Layers.
More infos about it here
Add this code to your function (hints are in the code comments):
// specify the property you want to animate
let zoomInAndOut = CABasicAnimation(keyPath: "transform.scale")
// starting from the initial value 1.0
zoomInAndOut.fromValue = 1.0
// to scale down you set toValue to 0.5
zoomInAndOut.toValue = 0.5
// the duration for the animation is set to 1 second
zoomInAndOut.duration = 1.0
// how many times you want to repeat the animation
zoomInAndOut.repeatCount = 5
// to make the one animation(zooming in from 1.0 to 0.5) reverse to two animations(zooming back from 0.5 to 1.0)
zoomInAndOut.autoreverses = true
// because the animation consists of 2 steps, caused by autoreverses, you set the speed to 2.0, so that the total duration until the animation stops is 5 seconds
zoomInAndOut.speed = 2.0
// add the animation to your button
button.layer.addAnimation(zoomInAndOut, forKey: nil)
Result:

This will zoom in and out the button without using additional images:
let timer = NSTimer.scheduledTimerWithTimeInterval(5, target: self, selector: "stopButtonAnimation", userInfo: nil, repeats: false)
let options = UIViewAnimationOptions.Autoreverse | UIViewAnimationOptions.Repeat | UIViewAnimationOptions.CurveEaseInOut
UIView.animateWithDuration(0.25, delay: 0, options: options,
animations: {
self.button.transform = CGAffineTransformMakeScale(0.5, 0.5)
}, completion:nil)
.....
func stopButtonAnimation() {
button.layer.removeAllAnimations;
}

Related

UIView Animation Chaining Doesn't Work Properly

I am trying to animate a simple Loading label text to show 3 dots after it, with each dot having a second of delay.
Here is what i tried:
func animateLoading() {
UIView.animate(withDuration: 1, animations: {self.yukleniyorLabel.text = "Yükleniyor."}, completion: { _ in
UIView.animate(withDuration: 1, animations: {self.yukleniyorLabel.text = "Yükleniyor.."}, completion: { _ in
UIView.animate(withDuration: 1, animations: {self.yukleniyorLabel.text = "Yükleniyor..."})
})
})
}
But what i got is the all 3 dots appear in 1 second alltogether. Not in order. See here: https://streamable.com/yiz6s
What am i doing wrong with the chaining? Thanks in advance.
UIView animate is only for animatable view properties such as frame and background color. self.yukleniyorLabel.text is not an animatable property. So you get no animation.
Just use a Timer or delayed performance to change the text at time intervals.
You can use the scheduled Timer for showing text with three dots on the label with animation: ->
var i = 0
var timer : Timer?
loaderLabel.text = "Loading"
timer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector:#selector(ViewController.setText), userInfo: nil, repeats: true)
#objc func setText() {
loaderLabel.text = loaderLabel.text! + "."
i += 1
if i >= 3 {
timer?.invalidate()
}
}
output with animation: -> Loading...

Cannot tap UIButton after having set alpha to 0.0, even when reset to 1.0

I have this simple code:
func tappedButton() {
self.button.alpha = 1.0
UIView.animate(withDuration: 1.0, delay: 4.0, options: .curveLinear, animations: {
self.button.alpha = 0.0
}) { _ in }
}
This function aims at showing a button again for 4 seconds before hiding it (with a 1 second animation). However, while the button is completely visible for these 4 seconds, tapping it doesn't work anymore.
Thanks for your help.
As per the documentation in for the method hittest(_:with:) of UIView https://developer.apple.com/documentation/uikit/uiview/1622469-hittest
This method ignores view objects that are hidden, that have disabled user interactions, or have an alpha level less than 0.01. This method does not take the view’s content into account when determining a hit. Thus, a view can still be returned even if the specified point is in a transparent portion of that view’s content.
This means that any view, particularly a button, with alpha 0.0 would not be touched.
However, the problem here is that the button is still visible, at least for you. This odd behavior occurs because the actual alpha value of the button is already setted to 0.0 when the animations starts. Animations work by changing the visual hierachy and transition the difference with the parameters you give to the function. In your case, you have two states: a view with a visible button visible and another view without the button. Only the visual part is animated but the corresponding values are already setted. A solution would be:
func tappedButton() {
self.button.alpha = 1.0
UIView.animate(withDuration: 1.0, delay: 4.0, options: .curveLinear, animations: { [weak self] in
self?.button.alpha = 0.01
}) { [weak self] _ in self?.button.alpha = 0.0 }
}
EDIT: This solution seems like a hack but works. I use this approach because the completion handler is always called with a true value.
func tapped() {
let duration = 1.0
let delay = 2.0
let delayDuration = delay + duration
UIView.animate(withDuration: duration, delay: delay, options: [.curveLinear, .allowUserInteraction], animations: { [weak self] in
self?.saveButton.alpha = 0.1
})
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayDuration, execute: { [weak self] in
self?.saveButton.alpha = 0.0
})
}
You need to use allUserInteraction in the options and also check for touches. The animation is added immediately and although you see the button to the system it is already hidden. What does this mean? It means you are watching a movie. But at least with userInteraction enabled you can check for touch events. This is great but how do we know the button is really showing or not? Well you have to use two different checks most likely. One that checks the true UIView alpha of the button and one check that checks the opacity on the presentation layer. I have never fully looked at the link between UIView animations and Core Animation except that I think UIView animations are a wrapper for Core Animations. UIView animations definitely update the view model immediately. So an alpha animation is most likely interpreted into an opacity animation on the layer. Armed with this we can check the opacity of the presentation layer on touches and see that the button is being clicked even if the view model thinks the alpha is 0. This check on the presentation layer will work as long as the opacity is above 0. So here you go.
import UIKit
class ViewController: UIViewController {
lazy var testButton : UIButton = {
let v = UIButton(frame: CGRect(x: 20, y: 50, width: self.view.bounds.width - 40, height: 50))
v.backgroundColor = UIColor.blue
v.addTarget(self, action: #selector(buttonClicked), for: .touchUpInside)
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(testButton)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 1.0, delay: 4.0, options: .allowUserInteraction, animations: {
self.testButton.alpha = 0
}, completion: nil)
//test the layer
//test the layer for opacity
if let presentation = testButton.layer.presentation()?.animation(forKey: "opacity"){
print("the animation is already added so normal clicks won't work")
}
}
#objc func buttonClicked(){
print("clicked")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if let touch = touches.first{
let location = touch.location(in: self.view)
if self.testButton.frame.contains(location){
//but what you might not know is the animation is probably already running
//and so the check above misses this
if let buttonPres = testButton.layer.presentation(),
let _ = buttonPres.animation(forKey: "opacity"),
let opacity = buttonPres.value(forKey: "opacity") as? CGFloat{
if opacity > 0{
buttonClicked()
}
}
}
}
}
}

I need to infinitely rotate a view until told to stop

There are so many posts similar to this that I have seen and none of them works.
Here is my code so far.
func startRotating(view : UIImageView, rotating : Bool) {
//Rotating the rotatingClockBarImage
UIView.animate(withDuration: 1.0, delay: 0.0, options: [.curveLinear], animations: {
view.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
}, completion: {finished in
UIView.animate(withDuration: 1.0, delay: 0.0, options: [.curveLinear], animations: {
view.transform = CGAffineTransform(rotationAngle: 0)
}, completion: nil)
})//End of rotation
continueRotating(view: view)
}
The original problem would be that I couldn't rotate a full 360 degrees. I figured that out by rotating half way and the other half in the completion.
The problem now is once this animation finishes, that's it. I have tried putting it in a while loop, for loop, calling two similar functions back and forth. Nothing works it just keeps freezing my app.
In a for loop, for example, that would run 3 times, I put a print(). The print writes to the console three times but the animation only happens once. Because of this I am thinking the animation is just cutting itself off before it even starts, and the final rotation is the only one that plays through. So I need to find a way to allow it to play each rotation through.
This shouldn't be that hard seeing that Apple had their planes rotate so easily in a former version of Xcode in a game app. I'm trying to avoid deleting and reinstalling the old version just so I can look at that code.
Actually it would be more easy:
extension UIView {
func rotate360Degrees(duration: CFTimeInterval = 1.0) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat.pi * 2
rotateAnimation.duration = duration
rotateAnimation.repeatCount = Float.infinity
self.layer.add(rotateAnimation, forKey: nil)
}
func stopRotating(){
self.layer.sublayers?.removeAll()
//or
self.layer.removeAllAnimations()
}
}
Then for rotating:
yourView.rotate360Degrees()
for stopping:
yourView. stopRotating()
Did you try calling startRotating again in the completion block of your second half turn ?
Note that you should do that conditionally with a "stop" flag of your own if you ver want it to stop.

Reset animation after removeAllAnimations()

With great help from the community I made an zoom in-and-out animation of a button image. The problem is, after the first run the button is disabled and wont work again before the app is relaunched. What I want is that after the button is clicked and the animation is finished it goes back to the starting point, and you can click it again. Anybody here that can help me with that?
#IBAction func coffeeButton(sender: UIButton) {
var timer = NSTimer.scheduledTimerWithTimeInterval(5, target: self, selector: "stopButtonAnimation", userInfo: nil, repeats: false)
let options = UIViewAnimationOptions.Autoreverse | UIViewAnimationOptions.Repeat | UIViewAnimationOptions.CurveEaseInOut
UIView.animateWithDuration(0.5, delay: 0, options: options, animations: {
self.button.transform = CGAffineTransformMakeScale(0.5, 0.5)
}, completion: nil)
}
func stopButtonAnimation(){
button.layer.removeAllAnimations()
}
Corrected your stopButtonAnimation function.
What applying transform does is setting the scale of your button to it's original value, so you are able to see your animation again.
func stopButtonAnimation(){
button.layer.removeAllAnimations()
button.layer.transform = CATransform3DIdentity
}
Animation in your original code was stopped at the scale value of 0.5 so next time you click the button you just don't see it (because it had been animating scale from 0.5 to 0.5).

How do you create a Swift animation class that flips two images

I'm spending my turkey day trying to construct a flip animation class that I have been struggling with for weeks.
The goal is this:
Flip two images repeatedly, quickly at first, then slow down and stop, landing on one of the two images that was chosen before the animation began.
Right now both images are in a container view, in a storyboard. Ive tried using transitionFromView transitionFlipFromTop and I think that could work, but at the time I was unable to get it to repeat.
I am in the process of reading the View Programming Guide, but its tough to connect the dots between it, and swift. Being new to programming in general does not help.
Here is where I'm at: I'm now using a CATransform to scale an image from zero height, to full height. Im thinking that if I can somehow chain two animations, the first one showing the first image scaling up, then back down to zero, then animate the second image doing the same thing, that should give me the first part. Then if I could somehow get those two animations to repeat, quickly at first, then slow down and stop.
It seems I need to know how to nest multiple animations, and then be able to apply an animation curve to the nested animation.
I planned on solving the part about landing on a particular image by having two of these nested animations, one of them would have an odd number of flips, the other an even number of flips. Depending on the desired final image, the appropriate animation gets called.
Right now, my current code is able to repeatedly scale an image from zero to full, a set number of times:
import UIKit
import QuartzCore
let FlipAnimatorStartTransform:CATransform3D = {
let rotationDegrees: CGFloat = -15.0
let rotationRadians: CGFloat = rotationDegrees * (CGFloat(M_PI)/180.0)
let offset = CGPointMake(-20, -20)
var startTransform = CATransform3DIdentity
startTransform = CATransform3DScale(CATransform3DMakeRotation(0, 0, 0, 0),
1, 0, 1);
return startTransform
}()
class FlipAnimator {
class func animate(view: UIView) {
let viewOne = view
let viewTwo = view
viewOne.layer.transform = FlipAnimatorStartTransform
viewTwo.layer.transform = FlipAnimatorStartTransform
UIView.animateWithDuration(0.5, delay: 0, options: .Repeat, {
UIView.setAnimationRepeatCount(7)
viewOne.layer.transform = CATransform3DIdentity
},nil)
}
}
Im going to keep chipping away at it. Any help, or ideas, or hints about a better way to go about it would be amazing.
Thanks.
I'm using this function to rotate image horizontally and to change the image during the transition.
private func flipImageView(imageView: UIImageView, toImage: UIImage, duration: NSTimeInterval, delay: NSTimeInterval = 0)
{
let t = duration / 2
UIView.animateWithDuration(t, delay: delay, options: .CurveEaseIn, animations: { () -> Void in
// Rotate view by 90 degrees
let p = CATransform3DMakeRotation(CGFloat(GLKMathDegreesToRadians(90)), 0.0, 1.0, 0.0)
imageView.layer.transform = p
}, completion: { (Bool) -> Void in
// New image
imageView.image = toImage
// Rotate view to initial position
// We have to start from 270 degrees otherwise the image will be flipped (mirrored) around Y axis
let p = CATransform3DMakeRotation(CGFloat(GLKMathDegreesToRadians(270)), 0.0, 1.0, 0.0)
imageView.layer.transform = p
UIView.animateWithDuration(t, delay: 0, options: .CurveEaseOut, animations: { () -> Void in
// Back to initial position
let p = CATransform3DMakeRotation(CGFloat(GLKMathDegreesToRadians(0)), 0.0, 1.0, 0.0)
imageView.layer.transform = p
}, completion: { (Bool) -> Void in
})
})
}
Don't forget to import GLKit.
This flip animation swift code appears little better:
let transition = CATransition()
transition.startProgress = 0;
transition.endProgress = 1.0;
transition.type = "flip";
transition.subtype = "fromRight";
transition.duration = 0.9;
transition.repeatCount = 2;
self.cardView.layer.addAnimation(transition, forKey: " ")
Other option is
#IBOutlet weak var cardView: UIView!
var back: UIImageView!
var front: UIImageView!
self.front = UIImageView(image: UIImage(named: "heads.png"))
self.back = UIImageView(image: UIImage(named: "tails.png"))
self.cardView.addSubview(self.back)
UIView.transitionFromView(self.back, toView: self.front, duration: 1, options: UIViewAnimationOptions.TransitionFlipFromRight , completion: nil)
Please check link
It seems I need to know how to nest multiple animations, and then be able to apply an animation curve to the nested animation.
I don't think so. I don't think you want/need to nest anything. You are not repeating an animation in this story; you are doing different animations each time (because the durations are to differ). This is a sequence of animations. So I think what you need to know is how to do either a keyframe animation or a grouped animation. That way you can predefine a series of animations, each one lasting a certain predefined duration, each one starting after the preceding durations are over.
And for that, I think you'll be happiest at the Core Animation level. (You can't make a grouped animation at the UIView animation level anyway.)

Resources