Animating frame occasionally works - ios

Here's my function that animates my view
func animateViewMoving (up:Bool, moveValue :CGFloat) {
let movementDuration:NSTimeInterval = 0.3
let movement:CGFloat = ( up ? -moveValue : moveValue)
self.view.setNeedsLayout()
UIView.animateWithDuration(movementDuration, animations: {
self.view.layoutIfNeeded()
self.view.frame.origin.y += movement
})
}
I call this function on these two functions of UITextFieldDelegate
func textFieldDidBeginEditing(textField: UITextField) {
animateViewMoving(true, moveValue: 200)
}
func textFieldDidEndEditing(textField: UITextField) {
animateViewMoving(false, moveValue: 200)
}
The problem is this- Whenever I run my build and open the view for the first time, the view shifts up on pressing the textField and shifts down when editing the textField ends. However if I go back to a previous view and enter the view again, the view does not shift up on pressing the textField. Why is animateWithDuration working irregularly?

Sorry I don't know swift so I'll just write my solution in objective-C.
I assume that you are using AutoLayout. If that's the case I suggest that when you animate a view, you animate it's constraints, not the frame of the view. I encountered a lot of problems before when I animate the frame of the view.
Here's a sample:
- (void)moveSubview
{
_subviewLeadingSuperLeading.constant = 20; //just a sample to move the subview horizontally.
[UIView animateWithDuration:0.5 animations:^
{
[self.view layoutIfNeeded];
}
completion:^(BOOL isFinished)
{
//Do whatever you want
}];
}
I hope this will help you. :)

Related

Unwanted UIView frame reset after inserting text in UITextField

I'm stuck with some funny problem and ran out of ideas how to solve it.
In one of my controllers I use a simple scheme of adjusting a view frame according to the keyboard appearance.
In UITextFieldDelegate method I initialise the controller's property firstResponder:
func textFieldDidBeginEditing(_ textField: UITextField) {
self.firstResponder = textField
}
Then I use UIKeyboard notifications selectors to change the frame of contentView:
override func keyboardWillShow(_ notifications: Notification) {
super.keyboardWillShow(notifications)
let info = notifications.userInfo
let keyboardFrame:CGRect = (info![UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let duration:Double = (info![UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
var bottomY:CGFloat!
if self.firstResponder == self.emailTextField{
bottomY = self.emailBottomLine.frame.origin.y + 80 + self.headerView.frame.height
}
else {
return
}
if bottomY >= keyboardFrame.origin.y {
let offset = bottomY - keyboardFrame.origin.y
UIView.animate(withDuration: duration, animations: {
self.contentView.frame.origin.y = -offset
})
}else{
UIView.animate(withDuration: duration, animations: {
self.contentView.frame.origin.y = self.contentViewOriginY
})
}
}
override func keyboardWillHide(_ notifications: Notification) {
super.keyboardWillHide(notifications)
let info = notifications.userInfo
let duration:Double = (info![UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
UIView.animate(withDuration: duration, animations: {
self.contentView.frame.origin.y = self.contentViewOriginY
})
}
And everything works fine until I start typing inside the emailTextField. Each tap on the keyboard causes the contentView reset to its original position without animation.
The question is what really causes this behavior? I'm totally confused and have checked whatever thing is possibly affects this. Please, help!!!
Make sure either:
A. Your contentView does not have any layout constraints attached to it, or else when you set its frame, its frame will be reset on the next layout pass back to what the constraints say the frame should be.
or:
B. Use a constraint to position your contentView's vertical offset relative to the keyboard instead of adjusting its frame.

Animation doesn't work very well in Swift

I have a view (grey background) and another over it with a UITextField.
When I tap on the grey background, I want the view with the UITextField to disappear with an animation (move to top)
When I tap on the return key (virtual keyboard) the animation is smooth, but when I tap on the grey background, it disappears without animation. When I comment out the vueRecherche.isHidden = true in the completion, it's ok, but I want to hide it!
here is my function
private func fermeRecherche() {
if self.constraintVueRecherche_Top.constant == -5 {
return
}
self.txtRercherche.endEditing(true)
UIView.animate(withDuration: 0.3, animations: {
self.constraintVueRecherche_Top.constant = -5
self.vueFondGris.alpha = 0.0
self.view.layoutIfNeeded()
}, completion: { termine in
self.vueRecherche.isHidden = true
self.vueFondGris.isHidden = true
})
}
with this call, the animation is smooth :
func textFieldDidEndEditing(_ textField: UITextField) {
fermeRecherche()
}
but not with this one :
let tap=UITapGestureRecognizer(target: self, action: #selector(self.tapFondGris(_:)))
vueFondGris.addGestureRecognizer(tap)
func tapFondGris(_ sender: UITapGestureRecognizer) {
fermeRecherche()
}
any ideas?
Thanks

How to move view down by animation?

In my app, I implement webview, and under that webview I have a one view and I want to move that view by animation like in Safari.
Here is my code:
func scrollViewWillBeginDragging(scrollView: UIScrollView) {
if scrollView.panGestureRecognizer.translationInView(scrollView.superview).y > 0 {
// scrolls down
print("UP")
viewbottom.hidden = false
viewHieght.constant = 45
} else {
print("DOWN")
viewbottom.hidden = true
viewHieght.constant = 0
}
}
In this code I am hiding the view while scroll down but I want to move it down slowly like Safari. So how can I do this?
use layoutIfNeeded() property with animateWithDuration
UIView.animateWithDuration(0.2, animations: { () -> Void in
viewHieght.constant = 45
self.view.layoutIfNeeded()
})
viewHeight.constant = max(0, min(45, scrollView.panGestureRecognizer.translationInView(scrollView.superview).y))
This should make the view move with the gesture but min be 0 and max be 45

UIView.animateWithDuration Not Animating Swift (again)

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.

How to stop animating back in swift?

When I start typing in uitextfield i want some animation to happen and it should not reverse back until i finish typing. I am using this code to animate:
func textFieldDidBeginEditing(textField: UITextField) {
println("begin")
UIView.animateWithDuration(1.0, animations:{
self.secondTextfield.frame = CGRectMake(self.secondTextfield.frame.origin.x + 500, self.secondTextfield.frame.origin.y, self.secondTextfield.frame.size.width, self.venueTextField.frame.size.height)
})
}
What I want to do is: when i start typing in first textfield, i want the second text field to hide out from view and when i finish typing, i want it to animate back in.
What my issue is: When I start typing the animation happens and it comes back to the original position.It doesn't wait for me to finish typing.
Why not change alpha to 0, intead change its frame?
You need to identify witch textfield is editing.
You need to deal with textFieldDidEndEditing to get second textField back.
Example:
func textFieldDidBeginEditing(textField: UITextField) {
if textField == firstTextField {
UIView.animateWithDuration(1.0, animations:{
self.secondTextfield.alpha = 0
})
}
}
func textFieldDidEndEditing(textField: UITextField) {
if textField == firstTextField {
UIView.animateWithDuration(1.0, animations:{
self.secondTextfield.alpha = 1
})
}
}
While not knowing exactly what the problem is, I've found a reasonable alternative to solving your problem:
Instead of animating your frame, animate the auto-layout constraint that defines your textField's x coordinate. For instance, the image below consists of two textFields. I want to animate the bottom one when I begin typing in the top one:
The vertical constraint is a centre x alignment that I want to animate. To achieve the effect, I connected an #IBOutlet to my NSLayoutConstraint:
I had my view controller adopt UITextFieldDelegate and implemented three methods:
extension ViewController: UITextFieldDelegate {
func textFieldDidBeginEditing(textField: UITextField) {
self.c.constant += 100 //c is my constraint
UIView.animateWithDuration(1.0) {
self.view.layoutIfNeeded()
}
}
func textFieldDidEndEditing(textField: UITextField) {
println("textFieldDidEndEditing")
self.c.constant -= 100
UIView.animateWithDuration(1.0) {
self.view.layoutIfNeeded()
}
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
a.resignFirstResponder()
return true
}
}
This method needs a bit of auto-layout know how, but I was able to achieve the effect you desired.

Resources