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.
Related
I have made a subclass of UIView that automatically adds a AVPlayerLayer along with the video controls. I need to implement a full screen button that takes the UIView and rotates it into fullscreen just like the YouTube app. The problem is the UIView does not cover the whole screen. I have a XLPagerTab below the UIView that contains the overview and stuff for the video.
This is my code to make the UIView full screen. It is inside the VideoPlayer Class which is a subclass of UIView. isExpanded is a boolean to know the state, smallScreenCenter is used to store the initial center of the view to switch back to the small screen.
func fullScreenTapped() {
if !isExpanded {
//Expand the video
UIView.animate(withDuration: 0.8, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.height, height: UIScreen.main.bounds.width)
self.center = CGPoint(x: UIScreen.main.bounds.size.width * 0.5, y: UIScreen.main.bounds.size.height * 0.5)
self.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi / 2))
self.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.transform = CGAffineTransform.identity
self.center = self.smallScreenCenter!
let height = UIScreen.main.bounds.width * 9 / 16
let videoPlayerFrame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: height)
self.frame = videoPlayerFrame
self.layoutSubviews()
}, completion: nil)
}
isExpanded = !isExpanded
}
The view rotates and all but does not cover the whole screen, it just looks like this.
Is the XLPagerTab interfering with the fullscreen? I am confused.
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 }
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'm trying to make a cool launch screen but it's a little bit hard. I would like to make my image appear the way this appears:
what I would like
But for the moment the only animation I succeeded to do is this:
what I've made
The image I use is a PNG with transparent border and the yellow drawing.
The code I use to have the animation I have
( "yellowSide" is actually the name of the outlet from the imageView that hold my image with the yellow drawing):
func animation() {
yellowSide.frame = CGRect(x: yellowSide.frame.origin.x, y: yellowSide.frame.origin.y, width: 0, height: 47)
yellowSide.alpha = 0
UIView.animate(withDuration: 0.5, delay: 2.0, options: .curveEaseIn, animations: {
var yellowSideFrame = self.yellowSide.frame
self.yellowSide.frame = CGRect(x: yellowSideFrame.origin.x, y: yellowSideFrame.origin.y, width: 243, height: 47)
self.yellowSide.alpha = 1
}, completion: nil)
}
As you guess the width / value I enter are the value that I need to have so the image looks the way I want when it's fully appeared.
You are changing the frame but you are not clipping out of frame image . you need to do like below .
// in ViewDidLoad method
yellowSide.clipsToBounds=true
func animation() {
yellowSide.frame = CGRect(x: yellowSide.frame.origin.x, y: yellowSide.frame.origin.y, width: 0, height: 47)
yellowSide.alpha = 0
UIView.animate(withDuration: 0.5, delay: 2.0, options: .curveEaseIn, animations: {
var yellowSideFrame = self.yellowSide.frame
self.yellowSide.frame = CGRect(x: yellowSideFrame.origin.x, y: yellowSideFrame.origin.y, width: 243, height: 47)
self.yellowSide.alpha = 1
}, completion: nil)
}
I'm trying to create an animation where an object moves from one keypoint to the next smoothly. As of right now the object does move to the different key points but instead of transitioning to each one as soon as it finishes one animation it automatically jumps to the next keypoint but with no transition animation in between. This is the function I'm using:
func keyframeAnimate () {
UIView.animateKeyframes(withDuration: 3, delay: 0, options: [], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1.0, animations: {
let translation = CGAffineTransform(translationX: 150, y: 150)
self.square.alpha = 0.5
self.square.transform = translation.rotated(by: CGFloat.pi).scaledBy(x: 2, y: 2)
})
UIView.addKeyframe(withRelativeStartTime: 1.01, relativeDuration: 1.0, animations: {
let translation = CGAffineTransform(translationX: 100, y: 400)
self.square.alpha = 1
self.square.transform = translation.rotated(by: -CGFloat.pi).scaledBy(x: 1, y: 1)
})
})
}
Any help is appreciated!
Your problem is in the relative start and relative duration. As the documentation explains, these parameters should be values between 0 and 1 because these are fractions of the entire animateKeyframes(withDuration: duration