I have two shortcuts.
Each of them has initial properties, such as font size and opacity.
Before animation, the values are
labelOne.font = labelOne.font.withSize(109)
labelOne.alpha = 1.0
labelTwo.font = labelTwo.font.withSize(40)
labelTwo.alpha = 0.7
After the animation, they should have these properties:
labelOne.font = labelOne.font.withSize(40)
labelOne.alpha = 0.7
labelTwo.font = labelTwo.font.withSize(109)
labelTwo.alpha = 1.0
For transformation, I use CGAffineTransform()
example:
labelOne.transform = CGAffineTransform(scaleX: //some value? , y: //someValue?)
But I'm new to programming and don't quite understand how it works. Tell me how to write this animation with font size resizing?
To achieve an animation on both labels, as you wished by controlling font size and alpha, you could achieve it like this:
// Before animation
labelOne.font = labelOne.font.withSize(109)
labelOne.alpha = 1.0
labelTwo.font = labelTwo.font.withSize(40)
labelTwo.alpha = 0.7
UIView.animate(withDuration: 1, animations: {
// This will be values set during the animation of 1 second
self.labelOne.font = labelOne.font.withSize(40)
self.labelOne.alpha = 0.7
self.labelTwo.font = labelTwo.font.withSize(109)
self.labelTwo.alpha = 1.0
})
Meanwhile, CGAffineTransform(scaleX:, y:) lets you scale the X,Y coordinates value of your view (labelOne for example), but to make that happen with an animation you have to put that inside the animate block:
// normal state
labelOne.transform = CGAffineTransform(scaleX: 1, y: 1)
labelTwo.transform = CGAffineTransform(scaleX: 0.7, y: 0.7)
UIView.animate(withDuration: 1, animations: {
// State reached through the animation
labelOne.transform = CGAffineTransform(scaleX: 0.7, y: 0.7)
labelTwo.transform = CGAffineTransform(scaleX: 1, y: 1)
})
To achieve a smoother transition play with:
UIView.transition(with: labelOne, duration: 0.25, options: .transitionCrossDissolve, animations: {
self.labelOne.font = UIFont.systemFont(ofSize: 40)
}) { _ in }
Related
I am trying to figure out why UIView.animate does not work as intended.
I know with UIView.animateKeyframes I can chain multiple types of animations together, but in this instance I am doing just one animation transform from the current size to 0.0.
The spinner image is already spinning using CABasicAnimation prior to this transform using:
let spinAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
spinAnimation.toValue = .pi * 2.0 // 360º spin
spinAnimation.duration = 1.4
spinAnimation.repeatCount = Float.greatestFiniteMagnitude // Repeat forever in loops.
spinAnimation.isCumulative = true
spinAnimation.isRemovedOnCompletion = false
self.spinnerImageView.layer.add(spinAnimation, forKey: self.spinnerAnimationKey)
UIView KeyFrame Animation
UIView.animateKeyframes(withDuration: Constants.AnimationDuration.standard, delay: 0.0, options: .calculationModeLinear, animations: {
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.4, animations: {
self.spinner.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)
})
}) { (_) in
super.endRefreshing()
}
UIView Animation
UIView.animate(withDuration: 0.4, delay: 0.0, options: .curveLinear, animations: {
self.spinner.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)
}) { (_) in
super.endRefreshing()
}
The keyframe animation works as intended where it shows the animating image shrink to 0.0, but the standard animate just causes the image to disappear. Shouldn't they act in the same way or is it because the image already has a CABasicAnimation layer that is causing the animate to not work properly? I would prefer to use .animate since I am only doing a transformation.
I want to change the button's scale and add a shadow effect when the button is clicked, but the added shadow effect is not complete, where is the problem, why is the shadow of the last button only complete?
#objc func ButtonOnClicking(_ sender:homePageBtn){
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.5, options: UIViewAnimationOptions.allowAnimatedContent, animations: {
sender.layer.masksToBounds = false
sender.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)
sender.layer.shadowColor = ColorHellp.getColor("333333").cgColor
sender.layer.shadowOffset = CGSize(width: 0, height: 0)
sender.layer.shadowRadius = 4
sender.layer.shadowOpacity = 0.3
}) { (isfinished) in
print("finished")
}
}
I can see, that also for the first button the shadow is complete, but it looks like, that the button is under the other ones.
So try something like bringSubViewToFront or change the z-index.
I have a subclassed imageview that I'd like to fade in, scale up, and rotate, then continue rotating while scaling back down and fading out.
I am using a UIView animate block with a completion handler to handle the shrinking back down.
The problem is it's not a fluid animation. Before the completion handler runs, the animation stops before running again. I need it to be one nice "swoop" of an animation.
Code below:
let duration: TimeInterval = 3.0
let rotate = CGAffineTransform(rotationAngle: CGFloat(Double.pi))
UIView.animate(withDuration: duration * 3, delay: 0, options: [.curveLinear], animations: {
// initial transform
self.alpha = 0
self.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)
// initial spin for duration of animaton
UIView.animate(withDuration: duration * 3, delay: 0.0, options: [.curveLinear], animations: {
self.transform = rotate
}, completion: nil)
// scaling and fading
UIView.animate(withDuration: duration, delay: 0, options: [.curveLinear], animations: {
UIView.setAnimationRepeatCount(3)
self.transform = self.transform.scaledBy(x: 0.8, y: 0.8)
self.alpha = 1
}) { (true) in
UIView.animate(withDuration: duration, animations: {
UIView.setAnimationRepeatCount(3)
self.transform = self.transform.scaledBy(x: 0.1, y: 0.1)
self.alpha = 0
})
}
}, completion: nil)
How can I get the animation to rotate the entire time while fading in and scaling up before scaling back down and fading out? The entire animation should last 3 seconds, and repeat 3 times. Thanks.
For your modified request I am expanding what you already saw using the block animations. As some have said, key frame animations may be better, regardless, here is the thought.
Create an animation that rotates the entire time by transforming the view.
Create another animation that does the scaling and fading based off the current transform (which is rotating). In this pass, I just created some variable to allow you to customize (and repeat) portions of the animation. I broke some things out to be clear and know I could refactor to write thing even more concise.
Here is the code
import UIKit
class OrangeView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
let duration: TimeInterval = 9.0
self.transform = CGAffineTransform.identity
// initial transform
self.alpha = 1
self.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)
// start rotation
rotate(duration: duration)
// scaling and fading
scaleUpAndDown(desiredRepetitions: 3, initalDuration: duration)
}
func rotate(duration: TimeInterval) {
UIView.animate(withDuration: duration/2.0,
delay: 0.0,
options: [.curveLinear], animations: {
let angle = Double.pi
self.transform = self.transform.rotated(by: CGFloat(angle))
}, completion: {[weak self] finished in
guard let strongSelf = self else {
return
}
if finished &&
strongSelf.transform != CGAffineTransform.identity {
strongSelf.rotate(duration: duration)
} else {
// rotation ending
}
})
}
func scaleUpAndDown(timesRepeated: Int = 0, desiredRepetitions: Int, initalDuration: TimeInterval) {
guard timesRepeated < desiredRepetitions,
desiredRepetitions > 0, initalDuration > 0 else {
self.transform = CGAffineTransform.identity
return
}
let repeatedCount = timesRepeated + 1
let scalingDuration = initalDuration/2.0/Double(desiredRepetitions)
UIView.animate(withDuration: scalingDuration,
delay: 0.0,
animations: {
let desiredOriginalScale: CGFloat = 0.8
let scaleX = abs(CGAffineTransform.identity.a / self.transform.a) * desiredOriginalScale
let scaleY = abs(CGAffineTransform.identity.d / self.transform.d) * desiredOriginalScale
self.transform = self.transform.scaledBy(x: scaleX, y: scaleY)
self.alpha = 1
}) { (true) in
UIView.animate(withDuration:scalingDuration,
delay: 0.0,
animations: {
let desiredOriginalScale: CGFloat = 0.1
let scaleX = abs(CGAffineTransform.identity.a / self.transform.a) * desiredOriginalScale
let scaleY = abs(CGAffineTransform.identity.d / self.transform.d) * desiredOriginalScale
self.transform = self.transform.scaledBy(x: scaleX, y: scaleY)
self.alpha = 0
}) { finshed in
self.scaleUpAndDown(timesRepeated: repeatedCount, desiredRepetitions: desiredRepetitions, initalDuration: initalDuration);
}
}
}
}
Finally here is another animated gif
I see the slight stutter in rotation at the start of the onCompletion.
I created a reduction with your code (shown below in the Blue View) and a variation in the Orange View. This was taken from the simulator and turned into an animated GIF, so speed is slowed down. The Orange View continues to spin as the complete transform just scales down.
This is the code for the layoutSubviews() for the Orange View
override func layoutSubviews() {
super.layoutSubviews()
let duration: TimeInterval = 3.0
let rotate = CGAffineTransform(rotationAngle: CGFloat(Double.pi))
// initial transform
self.alpha = 0
self.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)
// initial spin for duration of animaton
UIView.animate(withDuration: duration,
delay: 0.0,
options: [.curveLinear],
animations: {
self.transform = rotate;
},
completion: nil)
// scaling and fading
UIView.animate(withDuration: duration/2.0, animations: {
self.transform = self.transform.scaledBy(x: 0.8, y: 0.8)
self.alpha = 1
}) { (true) in
UIView.animate(withDuration: duration/2.0, animations: {
self.transform = self.transform.scaledBy(x: 0.1, y: 0.1)
self.alpha = 0
})
}
}
Try using CGAffineTransformConcat()
CGAffineTransform scale = CGAffineTransformMakeScale(0.8, 0.8);
self.transform = CGAffineTransformConcat(CGAffineTransformRotate(self.transform, M_PI / 2), scale);
I have a UIButton, and I would like to change its text by accessing its titleLabel attribute. However, the way I want the button to change text is by first shrinking down to a very tiny size, then changing instantly while it's invisible, then scaling back up. After looking through multiple posts on here, I have reached this:
let changeText = CATransition();
changeText.type = kCATransitionReveal;
changeText.duration = 0.0;
changeText.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionLinear);
submitButton.titleLabel?.layer.add(changeText, forKey: "changeTextTransition");
UIView.animateKeyframes(withDuration: 0.6, delay: 0, options: .calculationModeLinear, animations: {
//Zzzeeeewwwwwwwwww
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.5, animations: {
self.submitButton.titleLabel?.transform = self.submitButton.titleLabel!.transform.scaledBy(x: 0.001, y: 0.001);
})
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.0, animations: {
self.submitButton.titleLabel?.text = "Green";
})
//Wwwwwweeeeeeyyyyyppp
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5, animations: {
self.submitButton.titleLabel?.transform = self.submitButton.titleLabel!.transform.scaledBy(x: 1000, y: 1000);
})
}, completion: nil)
The problem is that this doesn't work. What I get is a quick flash of the words "New Text" as the label shrinks, and then when it scales back up, it's still "Old Text". It's so weird I can't even begin to wrap my head around what might be the cause. I think what's happening is that it plays the reveal transition on the new text before it even shrinks down (No idea why since I specified duration=0), then grows back the old text.
Here is what it looks like (with some background color change that I omitted above):
First off, congratulations for including sound effects in your code comments, glad it's not just me that does that.
The text property of a label cannot be animated, so it is applied immediately, spoiling your animation.
One solution, which I use a lot for complex animations, is to use snapshots. At the start of the animation, create a snapshot of the label, then set the alpha of the label to zero and update the text. Then, animate the transforms of the label and snapshot together, when they're small, make the label visible and the snapshot invisible, then animate back up.
That sounds more complicated than it is, here's code to run it in a playground:
import UIKit
import PlaygroundSupport
let view = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
view.backgroundColor = .blue
PlaygroundPage.current.liveView = view
let label = UILabel(frame: CGRect(x: 100, y: 100, width: 200, height: 200))
label.text = "Hello"
label.font = UIFont.systemFont(ofSize: 50)
label.textAlignment = .center
label.textColor = .white
view.addSubview(label)
let snapshot = label.snapshotView(afterScreenUpdates: true)!
view.addSubview(snapshot)
snapshot.frame = label.frame
label.alpha = 0
label.text = "Goodbye"
UIView.animateKeyframes(withDuration: 2, delay: 0, options: [.repeat, .autoreverse], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.45) {
label.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
snapshot.transform = label.transform
}
UIView.addKeyframe(withRelativeStartTime: 0.45, relativeDuration: 0.1) {
label.alpha = 1.0
snapshot.alpha = 0.0
}
UIView.addKeyframe(withRelativeStartTime: 0.55, relativeDuration: 0.45) {
label.transform = CGAffineTransform.identity
}
}) { _ in
snapshot.removeFromSuperview()
}
Which gives you this result:
It's not clear to me what you are trying to do with the CATransition - unless there's another effect you're looking for as well, that isn't helping your cause.
Let's say I scale a UILabel using a CGAffineTransformScale like so:
let scale = 0.5
text = UILabel(frame: CGRectMake(100, 100, 100, 100))
text.text = "Test"
UIView.animateWithDuration(2.0, delay: 0.0, options: UIViewAnimationOptions.CurveEaseIn, animations: {
self.text.transform = CGAffineTransformScale(self.text.transform, scale, scale)
}, completion: {(value : Bool) in
print("Animation finished")
})
This works great when I want to scale the UILabel by half. But if I were to call this same code again, it would end up with a scale of 0.25, as it scales again by half.
Would it be possible to use the CGAffineTransformScale to always scale to a size of half the original UILabel frame, instead of a scaling it cumulatively?
Swift 3:
text.transform = CGAffineTransform.identity
UIView.animate(withDuration: 0.25, animations: {
self.text.transform = CGAffineTransform(scaleX: scale, y: scale)
})
You are scaling the existing transform. Just create a new transform:
self.text.transform = CGAffineTransformMakeScale(scale, scale)
Swift version:
self.text.transform = CGAffineTransform.init(scaleX: scaleFactorX, y: scaleFactorY)