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...
I currently have the problem that the completion of the animation function ends before the animation itself does.
The array progressBar[] includes multiple UIProgressViews. When one is finished animating I want the next one to start animating and so on. But right now they all start at once.
How can I fix this?
#objc func updateProgress() {
if self.index < self.images.count {
progressBar[index].setProgress(0.01, animated: false)
group.enter()
DispatchQueue.main.async {
UIView.animate(withDuration: 5, delay: 0.0, options: .curveLinear, animations: {
self.progressBar[self.index].setProgress(1.0, animated: true)
}, completion: { (finished: Bool) in
if finished == true {
self.group.leave()
}
})
}
group.notify(queue: .main) {
self.index += 1
self.updateProgress()
}
}
}
The problem is that UIView.animate() can only be used on animatable properties, and progress is not an animatable property. "Animatable" here means "externally animatable by Core Animation." UIProgressView does its own internal animations, and that conflicts with external animations. This is UIProgressView being a bit over-smart, but we can work around it.
UIProgressView does use Core Animation, and so will fire CATransaction completion blocks. It does not, however, honor the duration of the current CATransaction, which I find confusing since it does honor the duration of the current UIView animation. I'm not actually certain how both of these are true (I would think that the UIView animation duration would be implemented on the transaction), but it seems to be the case.
Given that, the way to do what you're trying looks like this:
func updateProgress() {
if self.index < self.images.count {
progressBar[index].setProgress(0.01, animated: false)
CATransaction.begin()
CATransaction.setCompletionBlock {
self.index += 1
self.updateProgress()
}
UIView.animate(withDuration: 5, delay: 0, options: .curveLinear,
animations: {
self.progressBar[self.index].setProgress(1.0, animated: true)
})
CATransaction.commit()
}
}
I'm creating a nested transaction here (with begin/commit) just in case there is some other completion block created during this transaction. That's pretty unlikely, and the code "works" without calling begin/commit, but this way is a little safer than messing with the default transaction.
I see code to make text in UILabel to blink, but I am using Swift 2, and what changes does one make to have such text blink in Swift?
I just need this style only to alert the user of my app to start the game, then I don't need any other text to blink.
If you have provided some code it were simpler to answer but without it it's I can do:
let foo = UITextField()
UIView.animateWithDuration(0.3, animations: {() -> Void in
foo.alpha = 0.0
},
completion: { finished in
UIView.animateWithDuration(0.3, animations: {
foo.alpha = 1.0
})
})
You can do this by using animateWithDuration.
Here i am writing in viewDidLoad(). you can use this according to your requirement.
override func viewDidLoad() {
super.viewDidLoad()
self.myLabel.alpha=0
UIView.animateWithDuration(1, delay: 0.2, options:[.Repeat,.Autoreverse],
animations:{ self.myLabel.alpha=1.0}, completion: nil)
}
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.
This is iPad app by using SWIFT. It having two views.
1. starting_View
2. login_View
These two views are in same ViewController.
starting_View will be first view. By Clicking, NEXT button in first View, Starting View will move to left side by using animateDuration and same time, login_View will come from right. If we click Username/password fields (Any TextField), it will navigates to previous view.
Same time,,
if login_View will be first screen means, textField is working, keyboard is appearing.
But in animateDuration, i couldn't type. Kindly help me. Am using XCODE 6.1.
Code (from comment):
#IBAction func getStart_button(sender: UIButton) {
UIView.animateWithDuration(0.7, delay: 0.25, options: .CurveEaseOut, animations: {
self.clt_login_vw.frame = CGRectMake(450, 56, 574, 660)
}, completion:nil)
}
In General try to never change frames directly when using Autolayout.
The best way to move views have been for me to use CGAffineTransform.
let move = CGAffineTransformMakeTranslation(translation_X , translation_Y)
and inside your animation closure:
self.clt_login_vw.transform = move
Then to return it back just do inside the animation Closure:
self.clt_login_vw.transform = CGAffineTransformIdentity
Resume:
let moveOut = CGAffineTransformMakeTranslation(translation_X , translation_Y)
#IBAction func getStart_button(sender: UIButton) {
UIView.animateWithDuration(0.7, delay: 0.25, options: .CurveEaseOut, animations: {
[weak self] // Remember no not create cycles
self!.clt_login_vw.transform = moveOut
}, completion:nil)
}
Edit:
Im gonna add Noah Witherspoon's option.
Another way to do this is getting the constraint that is holding your view horizontally, once you get it, you change its constant and call layoutIfNeeded() inside the closure:
#IBAction func getStart_button(sender: UIButton) {
constraint.constant = newConstant // enough to make it off the screen
UIView.animateWithDuration(0.7, delay: 0.25, options: .CurveEaseOut, animations: {
[weak self] // Remember no not create cycles
self!.view.layoutIfNeeded()
}, completion:nil)
}
The way to get the specific constraint, I'm more friend of making IBOutlets, so I get easily its reference in Code.
That is if you didn't make it by code.