I've been searching online for an answer to my question but there doesn't seem to be any solutions. I have a UILabel which contains two interchangeable icons (from FontAwesome). I would like to create an animation where it changes the UILabels text from one to the other repeatedly.
What I have so far is an animation which calls itself again and again. It seemed to look fine on the simulator but when I ran it on the phone it didn't work how I wanted it to. I seem to almost be there but my code creates some sort of flash when it fades out
func animateLabel() {
self.runningPersonLabel.text = self.runningPersonLabel.text == "ICON 1" ? "ICON 2" : "ICON 1"
self.runningPersonLabel.sizeToFit()
self.runningPersonLabel.center = modalContainer.boundsCenter
self.runningPersonLabel.frameTop = titleLabel.frameBottom + 40
UIView.animate(withDuration: 2, delay: 0, options: [.autoreverse], animations: {
self.runningPersonLabel.alpha = 1.0
}, completion: { finished in
self.runningPersonLabel.alpha = 0.0
self.animateLabel()
})
}
Try with this class:
import UIKit
#IBDesignable class FadingLabel: UILabel
{
// The secondary text
#IBInspectable var secondaryText:String?
var primaryText:String?
// Animation time, is divided by 2
#IBInspectable var animationTime:TimeInterval = 1
// Set this flag to true to stop animation
var stop = true
// Start the animation
func startAnimating()
{
stop = false
if primaryText == nil
{
primaryText = self.text
}
fadeAnim()
}
// Stop the animation
func stopAnimating(_ sender: UIButton)
{
stop = true
}
#objc private func fadeAnim()
{
if stop
{
return
}
// Fade out
UIView.animate(withDuration: animationTime / 2, animations: {
self.alpha = 0
}) { (complete) in
UIView.animate(withDuration: self.animationTime / 2, animations: {
if self.text == self.primaryText
{
self.text = self.secondaryText
}
else
{
self.text = self.primaryText
}
self.alpha = 1
}, completion: { (complete2) in
self.fadeAnim()
})
}
}
}
Usage:
put a label in your view controller;
set the label class to FadingLabel;
set the secondary text and the animation time in the storyboard inspector
call the methods startAnimating or stopAnimating as needed.
I'm trying to rotate the background colour endlessly between two different gradients, but the actual transition isn't lasting as long as expected (it lasts for less than a second and there's no delay between each transition).
var backgroundColours = [CAGradientLayer()]
var backgroundLoop = 0
override func viewDidLoad() {
super.viewDidLoad()
backgroundColours = [Colors.init().one, Colors.init().two] // These are two CAGradientLayers but I haven't included their code
backgroundLoop = 0
self.animateBackgroundColour()
}
func animateBackgroundColour () {
backgroundLoop = (backgroundLoop + 1) % (backgroundColours.count)
UIView.animate(withDuration: 2.0, delay: 2.0, options: .curveLinear, animations: {
self.view.backgroundColor = UIColor.clear
let backgroundLayer = self.backgroundColours[self.backgroundLoop]
backgroundLayer.frame = self.view.frame
self.view.layer.insertSublayer(backgroundLayer, at: 0)
}, completion: { (Bool) -> Void in
self.animateBackgroundColour()
})
This is all inside the ViewController Class
UIView.animate animates only about five specialized view properties. You are not animating any of those properties. What you want is layer animation (e.g. CABasicAnimation).
I'm trying to make an animation where the color red pulses on the screen and when the screen is tapped the speed of the pulse increases. Right now I have two issues. One, is that the animation does not occur, the second is that the animation must speed up without restarting the animation. I want the animation to speed up from whatever point it is at when the screen is tapped.
class ViewController: UIViewController {
var tapCount: Int = 0
var pulseSpeed: Double = 3
let pulseAnimLayer = CALayer()
let pulseAnim = CABasicAnimation(keyPath: "Opacity")
override func viewDidLoad() {
super.viewDidLoad()
counter.center = CGPoint(x: 185, y: 118)
pulseAnim.fromValue = 0.5
pulseAnim.toValue = 1.0
pulseAnim.duration = 3.0
pulseAnim.autoreverses = true
pulseAnim.repeatCount = .greatestFiniteMagnitude
pulseAnimLayer.add(pulseAnim, forKey: "Opacity")
}
func pulseAnimation(pulseSpeed: Double) {
UIView.animate(withDuration: pulseSpeed, delay: 0,
options: [UIViewAnimationOptions.repeat, UIViewAnimationOptions.autoreverse],
animations: {
self.red.alpha = 0.5
self.red.alpha = 1.0
}
)
}
#IBOutlet weak var red: UIImageView!
#IBOutlet weak var counter: UILabel!
#IBAction func screenTapButton(_ sender: UIButton) {
tapCount += 1
counter.text = "\(tapCount)"
pulseSpeed = Double(3) / Double(tapCount)
pulseAnim.duration = pulseSpeed
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
I didn't read this carefully enough the first time.
You've got an odd mix here of core animation and UIView animation. The two exist at different levels. Core Animation will allow you to animate a property on a view (say it's background color).
UIView animation is built on top of Core Animation and makes it easier to animate view changes automatically by building the underlying Core Animations.
You start with Core Animation when you create a CABasicAnimation but then you have a function, pulseAnimation which looks to be doing something using UIVIew animations. I think you can get rid of that function entirely.
When you add your CABasicAnimation you tell it to change the value of the layer's "Opacity". What you probably want there is "opacity" (lower case).
Your animation is being applied to a new CALayer that you've created... but you don't seem to ever put that layer into the view (so it is never drawn). More likely than not what you should probably do is attach your animation to the view's layer and just allow that layer to have it's color animated.
To add one layer to another use addSublayer (https://developer.apple.com/reference/quartzcore/calayer/1410833-addsublayer). The view's layer from your code should be available as self.view.layer though you may have to use it in viewWillAppear instead of in viewDidLoad
You need to set the alpha of the red imageview to 0.0 before animating
func pulseAnimation(pulseSpeed: Double) {
UIView.animate(withDuration: pulseSpeed, delay: 1.0,
options: [UIViewAnimationOptions.repeat, UIViewAnimationOptions.autoreverse],
animations: {
self.red.alpha = 0.5
self.red.alpha = 1.0
}
)
}
override func viewDidAppear(_ animated: Bool) {
self.red.alpha = 0.0
counter.center = CGPoint(x: 185, y: 118)
}
Note: I’ve already checked the following stack overflow issues:
27907570, 32229252, 26118141, 31604300
All I am trying to do is fade animate in a view (by alpha) when called by an IBAction attached to a button. Then reverse when a button on the view is hit.
My wrinkle may be that I'm using a secondary view that is on the ViewDock in the storyboard View. The view is added to the subview at the time of viewDidLoad where the frame/bounds are set to the same as the superview (for a full layover)
The reason this is done as an overlay view since it is a tutorial indicator.
The result (like many others who've listed this problem) is that the view (and contained controls) simply appears instantly and disappears as instantly. No fade.
I have tried animationWithDuration with delay, with and without completion, with transition, and even started with the old UIView.beginAnimations.
Nothing is working. Suggestions warmly welcomed.
The code is about as straight forward as I can make it:
Edit: Expanded the code to everything relevant
Edit2: TL;DR Everything works with the exception of UIViewAnimateWithDuration which seems to ignore the block and duration and just run the code inline as an immediate UI change. Solving this gets the bounty
#IBOutlet var infoDetailView: UIView! // Connected to the view in the SceneDock
override func viewDidLoad() {
super.viewDidLoad()
// Cut other vDL code that isn't relevant
setupInfoView()
}
func setupInfoView() {
infoDetailView.alpha = 0.0
view.addSubview(infoDetailView)
updateInfoViewRect(infoDetailView.superview!.bounds.size)
}
func updateInfoViewRect(size:CGSize) {
let viewRect = CGRect(origin: CGPointZero, size: size)
infoDetailView.frame = viewRect
infoDetailView.bounds = viewRect
infoDetailView.layoutIfNeeded()
infoDetailView.setNeedsDisplay()
}
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
updateInfoViewRect(size)
}
func hideInfoView() {
AFLog.enter(thisClass)
UIView.animateWithDuration(
2.0,
animations:
{
self.infoDetailView.alpha = 0.0
},
completion:
{ (finished) in
return true
}
)
AFLog.exit(thisClass)
}
func showInfoView() {
AFLog.enter(thisClass)
UIView.animateWithDuration(
2.0,
animations:
{
self.infoDetailView.alpha = 0.75
},
completion:
{ (finished) in
return true
}
)
AFLog.exit(thisClass)
}
// MARK: - IBActions
#IBAction func openInfoView(sender: UIButton) {
showInfoView()
}
#IBAction func closeInfoView(sender: UIButton) {
hideInfoView()
}
Please note, I started with the following:
func showInfoView() {
UIView.animateWithDuration(2.0, animations: { () -> Void in
self.infoDetailView.alpha = 0.75
})
}
func hideInfoView() {
UIView.animateWithDuration(2.0, animations: { () -> Void in
self.infoDetailView.alpha = 0.00
})
}
If you infoDetailView is under auto layout constraints you need to call layoutIfNeeded on the parent view inside animateWithDuration:
func showInfoView() {
self.view.layoutIfNeeded() // call it also here to finish pending layout operations
UIView.animate(withDuration: 2.0, animations: {
self.infoDetailView.alpha = 0.75
self.view.layoutIfNeeded()
})
}
Theoretically this should not be needed if you just change the .alpha value, but maybe this could be the problem in this case.
There are several strange things I can see,
first, remove:
infoDetailView.layoutIfNeeded()
infoDetailView.setNeedsDisplay()
Usually you don't need to call those methods manually unless you know exactly what you are doing.
Also, when you are changing the size:
infoDetailView.frame = viewRect
infoDetailView.bounds = viewRect
You never need to set both bounds and frame. Just set frame.
Also, you should probably make sure that the view actually doesn't ignore the frame by setting:
infoDetailView.translatesAutoresizingMaskIntoConstraints = true
Instead of resetting the frame, just set autoresize mask:
infoDetailView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
Resulting in:
override func viewDidLoad() {
super.viewDidLoad()
// Cut other vDL code that isn't relevant
setupInfoView()
}
func setupInfoView() {
infoDetailView.alpha = 0.0
infoDetailView.translatesAutoresizingMaskIntoConstraints = true
infoDetailView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
infoDetailView.frame = view.bounds
view.addSubview(infoDetailView)
}
func hideInfoView() {
...
}
I think this should actually help because immediate animations are often connected to size problems.
If the problem persists, you should check whether the infoDetailView in your animation is the same object as the infoDetailView you are adding to the controller.
For others looking to start an animation immediately when a view loads...
The animation won't work if you call UIView.animate(...) inside viewDidLoad. Instead it must be called from the viewDidAppear function.
override func viewDidAppear(_ animated: Bool) {
UIView.animate(withDuration: 3) {
self.otherView.frame.origin.x += 500
}
}
If the animation does not seem to execute then consider examining the state of each of your views, before you enter the animation block. For example, if the alpha is already set to 0.4 then the animation that adjusts your view alpha, will complete almost instantly, with no apparent effect.
Consider using a keyframe animation instead. This is what a shake animation in objective c looks like.
+(CAKeyframeAnimation*)shakeAnimation {
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
animation.values = #[[NSValue valueWithCATransform3D:CATransform3DMakeTranslation(-10.0, 0.0, 0.0)],
[NSValue valueWithCATransform3D:CATransform3DMakeTranslation(10.0, 0.0, 0.0)]];
animation.autoreverses = YES;
animation.repeatCount = 2;
animation.duration = 0.07;
return animation;
}
Here is a post that shows you how to adjust alpha with keyframes https://stackoverflow.com/a/18658081/1951992
Make sure infoDetailView's opaque is false.
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/#//apple_ref/occ/instp/UIView/opaque
This property provides a hint to the drawing system as to how it should treat the view. If set to true, the drawing system treats the view as fully opaque, which allows the drawing system to optimize some drawing operations and improve performance. If set to false, the drawing system composites the view normally with other content. The default value of this property is true.
Try Below code. Just play with alpha and duration time to perfect it.
Hide func
func hideInfoView() {
AFLog.enter(thisClass)
UIView.animateWithDuration(
2.0,
animations:
{
self.infoDetailView.alpha = 0.8
},
completion:
{ (finished) in
UIView.animateWithDuration(
2.0,
animations:
{
self.infoDetailView.alpha = 0.4
},
completion:
{ (finished) in
self.infoDetailView.alpha = 0.0
}
)
}
)
AFLog.exit(thisClass)
}
Show func
func showInfoView() {
AFLog.enter(thisClass)
UIView.animateWithDuration(
2.0,
animations:
{
self.infoDetailView.alpha = 0.3
},
completion:
{ (finished) in
UIView.animateWithDuration(
2.0,
animations:
{
self.infoDetailView.alpha = 0.7
},
completion:
{ (finished) in
self.infoDetailView.alpha = 1.0
}
)
}
)
AFLog.exit(thisClass)
}
I've replicated your code and it work well, it's all ok.
Probably you must control constraints, IBOutlet and IBActions connections. Try to isolate this code into a new project if it's necessary.
Update: my code
and my storyboard and project folder photo:
Every object (view and buttons) are with default settings.
I've commented all AFLog lines (probably it's only any more "verbose mode" to help you) , the rest of your code is ok and it do what do you aspected from it, if you press open button the view fade in, and when you tap close button the view fade out.
PS Not relevant but i'm using xCode 7.3 , a new swift 2.2 project.
Use this code:
Swift 2
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.infoDetailView.alpha = 0.0
})
Swift 3, 4, 5
UIView.animate(withDuration: 0.3, animations: { () -> Void in
self.infoDetailView.alpha = 0.0
})
Have you tried changing your showInfoView() to something more like toggleInfoView?
func toggleInfoView() {
let alpha = CGFloat(infoDetailView.alpha == 0 ? 1 : 0)
infoDetailView.alpha = alpha //this is where the toggle happens
}
It says that if your view's alpha is 0, then change it to 1. Else, make it 0.
If you need that to happen in an animation, try
#IBAction func openInfoView(sender: UIButton) {
UIView.animate(withDuration: 2.0, animations: {
self.toggleInfoView() //fade in/out infoDetailView when animating
})
}
You'll still want to keep that infoDetailView.alpha = 0.0 where you have it, coming from the viewDidLoad.
For UILabel component try to changes layer's background color instead.
Try this (Tested on Swift 4):
UIView.animate(withDuration: 0.2, animations: {
self.dateLabel.layer.backgroundColor = UIColor.red.cgColor;
})
Had a similar issue with animation not being performed.
Changed the function call use perform(aSelector: Selector, with: Any?, afterDelay: TimeInterval) in the form of perform(#selector(functionThatDoesAnimationOfAlphaValue), with: nil, afterDelay: 0) and it worked. Even with a TimeInterval set to 0.
In case someone else comes here wondering for a solution.
I am a beginner in swift. I am building a single view tip calculator app and i only want some animation to happen when the app loads for the first time. And not happen every time i edit something.
Here is my code:
I am hiding the container outside the view when the app loads
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
billView.center.y -= (billView.frame.height)/2
}
And animating it to move down
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
UIView.animateWithDuration(0.8, delay: 0.0,
options: [.CurveEaseOut], animations: {
self.billView.center.y = 210 }, completion: nil)
print(billView.center.y)
}
This works fine when the app loads. Unfortunately this animation keeps happening again and again anytime I interact with the app.
Like this animation happens when this function is called (which is when any information is entered on the text field or the slider is moved)
let billAmount = NSString(string: billField.text!).doubleValue
var tipPercentages = [0.15, 0.18, 0.22]
let tipPercentage = tipPercentages[segTipControl.selectedSegmentIndex]
let percentLabel = round(tipPercentage * 100)
selectedbillPercentageLabel.text = "+\(percentLabel)%"
let tipAmount = billAmount * tipPercentage
let totalAmount = billAmount + tipAmount
let roundedTotalAmount = round(totalAmount * 100)/100
totalLabel.text = "$\(roundedTotalAmount)"
let splitSelected = Double(splitValueSlider.value)
let intsplitSelected = Int(splitSelected)
splitLabel.text = "\(intsplitSelected)"
let splitDollarAmount = roundedTotalAmount/splitSelected
let roundedSplitDollarAmount = round(splitDollarAmount * 100)/100
splitAmount.text = "$\(roundedSplitDollarAmount)"
Is there a way to avoid calling the method viewDidLayoutSubviews() when any data is entered in the app.
animated gif
Rather than avoiding calling viewDidLayourSubviews, you could add a simple boolean flip to show your animation only once. Add a boolean property that you initialize to false; show animation on condition of this property being false, and flip the boolean after the animation has been shown.
var animationHasBeenShown = false // class property
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if !animationHasBeenShown {
UIView.animateWithDuration(0.8, delay: 0.0,
options: [.CurveEaseOut], animations: {
self.billView.center.y = 210 }, completion: nil)
print(billView.center.y)
animationHasBeenShown = true
}
}
Alternatively, consider placing your animation in another context; does it really need to be in viewDidLayoutSubviews() (depends on the context of your app, but possibly rather in viewDidAppear()?)