I'm currently working on a iOS (swift 3) app. I have a simple vertical stack view containing 2 horizontal stack views. In some cases I want to hide the bottom one. I do so by using the following code
UIView.animate(withDuration: 3) {
self.bottomStackView.isHidden = true;
};
The animation shown below doesn't really do what I would expect:
While the border of the buttons is animated properly when hiding, the text inside each button doesn't seem to be affected until the very end. Any idea as to how I could fix this?
I kept doing some research on the subject, and it seems like most articles were suggesting that using stacks to perform animation would work fine. However I have also found that animations would only work with animatable properties, isHidden not being one of them.
In the end after some trial and errors I have found that isHidden can be animated with stack views, but you can expect children to misbehave. So far the only workaround I have found is like so:
let duration = 0.5;
let delay = 0;
UIView.animate(withDuration: duration, delay: delay, animations: {
self.bottomStack.isHidden = self.hideBottomStack;
})
UIView.animate(withDuration: duration/2, delay: delay, animations: {
self.bottomStack.alpha = 0;
})
You'll note here that I basically "turn" the alpha property down to 0 in half the time I take to hide the stack. This has the effect to hide the text before it overlaps with the upper stack. Also note that I could also have decided to do something like this:
UIView.animate(withDuration: duration, delay: delay, animations: {
self.bottomStack.alpha = 0;
}, completion: { (_) in
self.bottomStack.isHidden = true;
})
This would also hide the bottom stack, but you lose the hiding motion in favor of a fading motion and hide the stack once the fading is done.
I'm not sure about this, I think stackviews can cause weird behaviour sometimes. Have you tried adding "self.view.layoutIfNeeded()" inside the UIView.animate block? Like this:
UIView.animate(withDuration: 3) {
self.bottomStackView.isHidden = true
self.view.layoutIfNeeded()
}
I think it should also work if you put the "self.bottomStackView.isHidden = true" above the UIView.animate, not sure though, not an expert at it.
Also, I don't think you need to use ";" after your line of code in swift :)
Related
When I click on an a segment of my UISegmentedControl, I want one of two UIViews to be displayed. But how do I arrange it, that I only show the new view. Currently I am calling thisview.removeFromSuperview() on the old one, and then setup the new all from scratch. I also tried setting all the HeightConstants of the views subviews to zero and then set the heightConstants of the view itself to zero but I'd rather avoid that constraints-surgery..
What better approaches are there?
Agree with #rmaddy about using UIView's hidden property, a nice simple way to cause a view to not be drawn but still occupy its place in the view hierarchy and constraint system.
You can achieve a simple animation to make it a bit less jarring as follows:
UIView.animate(withDuration:0.4, animations: {
myView.alpha = 0
}) { (result: Bool) in
myView.isHidden = true
}
This will fade the alpha on the view "myView", then upon completion set it to hidden.
The same animation concept can be used also if you've views need to re-arrange themselves, animating layout changes will be a nice touch.
Based on #rmaddy and #CSmiths answer, I built the following function:
func changeView(newView: UIView, oldView: UIView) {
newView.isHidden = false
newView.alpha = 0
UIView.animate(withDuration:0.4, animations: {
oldView.alpha = 0
newView.alpha = 1
}) { (result: Bool) in
oldView.isHidden = true
}
}
I feel dumb now for all the hours I spent on that constraint-surgery. :|
I was wondering how can i show a 'No Internet Connection' Just how like Instagram does it,
As an Example :
That see-through custom message animating to show under the navigationController . Would really love to get this to my project ,
thank you for you help
So here's a pic of the storyboard like this :-
"No internet connection" is a label, and the red view underneath is just to test the see through property of the label. If you are designing the UI in code, you can probably make a label similar to mine and place it to the top of the Navigation bar by using it's frame property.
The button here I'm using is just to show the label pop up on the scene (since it's just a demo answer). In your case, if the internet is not available, you will proceed to show the pop up.
So if you are making the UI in code, make sure to make the label in the viewDidLoad method. I have made an IBOutlet and the viewDidLoad now looks like this:-
override func viewDidLoad() {
super.viewDidLoad()
let transform = CGAffineTransform(translationX: 0, y: -label.frame.height)
label.alpha = 0
label.transform = transform
}
On the view loading, I'm moving the label behind the navigation bar, using CGAffineTransform. The distance, how much to move up is the label's height, since we don't want any part to be clipped on the scene.
Next step, is just a fix. I'm making alpha = 0, because navBar is translucent is nature and hence will change it's colour, since our label is behind it. So setting alpha to 0, takes care of it, and in third step apply the transform.
Now, if the internet connection is not available, we should pop out the label under the navBar. The code will look something like this:-
fun checkInternet() {
// called by some of your observer, which checks for changes in internet connection
if !isInternetAvailable {
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0, options: .curveLinear, animations: {
self.label.alpha = 0.5
self.label.transform = .identity
}, completion: nil)
}
}
So here, I'll show the pop up with an animation using UIView.animate with some spring damping, so it has a nice bouncy effect to it. I'm setting the alpha to 0.5, since you mentioned you want a see through label, and I'm setting the label to a transform which will bring it back to it's original position when it was created, that's why I'm using .identity.
You can play around usingSpringWithDamping values and change options to have different effects.
To make proper animation of a uiview with constraints u must set the new constraint value and then call theView.layoutIfNeeded() , for swift 3 this doesnt work and instead of calling from the view's whos constraint is changed . it must be called from the upper view like this : self.view.layoutIfNeeded().
ex :
UIView.animate(withDuration: 0.1,
delay: 0.1,
options: UIViewAnimationOptions.curveEaseIn,
animations: { () -> Void in
constraintHeight.constant = 10.00
// instead of myView.layoutIfNeeded() // Swift 2
self.view.layoutIfNeeded() // Swift 3
}, completion: { (finished) -> Void in
// ....
})
The problem is, in my case i did the change and the way im using this animation is for a bottomview (a bottom bar/ banner view) that hides when scrolling a tableview down and comes up when going all the way to the top in the tableview. now that i have changed the proper code for swift 3 using self.view.layoutIfNeeded() , the tableview acts wierd, slows down, rows start appearing as fading in or is just way slow to present, when scrolling down and up the tableview's sections comes jumping or moving in slow motion, also have seem memory gone up from 80mb to 100mb . if i eliminate the line in the code, i dont get the animation, the view just appears and dissapears with the scrolling of the tableview, but... i dont get the strange behavior. i have also checked the views hierarchy to check somehow is not creating wierd 100 views replicating or something.. any hints on how can i fix this . all of this was just working fine in swift 2 using theView.layoutIfneeded() but now that the call is being madein the upper view.. omg..wierd acting
Question comes from Swift 3 UIView animation solution.
Try this to force layout changes to superview
//Force to complete all changes.
self.view.superview.layoutIfNeeded()
//Update constant
constraintHeight.constant = 10.00
UIView.animate(withDuration: 0.1,
delay: 0.1,
options: UIViewAnimationOptions.curveEaseIn,
animations: { () -> Void in
self.view.superview.layoutIfNeeded()
}, completion: { (finished) -> Void in
// ....
})
Solution to my needs . thanks all for sharing your answers!.
my view hierarchy
- root view
- uitableView
- bottomBarView
- bottomBannerView
Beacause of that hierarchy i couldnt use self.view.layoutIfNeeded() or self.bottomBarView.superview?.layoutIfneeded(), as it was calling to layout the same superview which also host the tableview and for which im triggering this function if scrolling is more than 10 and also if it less.. so its always trigerring the layoufIfneededmethod. i had to do what i thought from the beginning.
The correct way is to make a container view hosting the bottombarview and the banner view, have it constraint to bottom of the root super view and constraint the bottomBarView and bottomBannerView to IT.
View hierarchy now is .
-root view
-uitableView
-containerBottomView
-bottomBarView
-bottomBannerView
This way i can call self.bottomBarView.superview?.layoutIfNeeded() , and it wont be triggering on the root view which also host the uitableview. it correctly triggers to layout the containerBottomView.
UIView.animate(withDuration: 0.5, delay: 0.3, options: [.repeat, .curveEaseOut, .autoreverse], animations: {
// perform your animation here .
self.username.center.x += self.view.bounds.width
self.view.layoutIfNeeded()
}, completion: nil)
I am trying to animate show/hide of search bar using below code (The search bar should come from left and expand to right within 1-2 seconds). However, it doesn't animate and searchBar is immediately shown no matter how much time I put. I noticed following:
Duration is not respected
Not even delay is respected
Animation is not happening. Component is immediately shown
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
//code to get selected value...
//Hide the collection view and show search bar
UIView.animateWithDuration(10.0,
delay: 0.0,
options: UIViewAnimationOptions.TransitionCrossDissolve,
animations: {
self.searchBar.hidden = false
self.searchBar.frame = CGRectMake(0, 0, 300, 44) //This doesn't work either
},
completion: { (finished: Bool) in
return true
})
}
I am using Xcode 7, iOS 8 and Swift 2.0. I have seen at other solution, but none of them works for me. Kindly help...
Update: It worked with below code. However, it used default animation option of UIViewAnimationOptionCurveEaseInOut and TransitionNone
UIView.animateWithDuration(0.7,
animations: {
self.searchBar.alpha = 1.0
self.searchBarRect.origin.x = self.searchBarRect.origin.x + 200
self.searchBar.frame = self.searchBarRect
},
completion: { (finished: Bool) in
return true
})
Before the animateWithDuration, set the XPosition to -200 and YPosition to -200.
Like Daniel Suggested, you need to also change the alpha from something other than 1.0 in order to see it "fade in".
Are you using autolayout? If you are, you may want to animate the constraints instead of the frame.
Finally, are you sure you've wired up the search bar properly?
Edit: PS, I would keep the duration to something like 3.0 or 5.0. 10.0 will take forever and may make you think that it isn't doing anything because of how long it takes. Make it 3.0 or 5.0 (max) just for testing and then decrease to where you want it.
I'm struggling to figure out how to allow user interaction with a view as it's being animated.
Here's the situation: I have a UIView cardView which holds card subviews. The cards are draggable tiles, similar to how the cards in Tinder are draggable/swipeable.
I am trying to fade out the card using animateWithDuration by animating to cardView.alpha = 0. Logically, this will also fade out all of the subviews (card objects). In this specific case, I am only targeting one card subview. However, during the animation, I am unable to drag/interact with the card.
Here is the code I'm using:
UIView.animateWithDuration(
duration,
delay: 0,
options: UIViewAnimationOptions.AllowUserInteraction,
animations: {self.cardView.alpha = 0}
) {
_ in
println("Card faded out")
card.removeFromSuperview()
}
Why doesn't this work? Any help will be appreciated. Thank you!!
I think you can find the answer in this previous post.
The interesting bit of the post is:
UIView's block animation by default blocks user interaction, and to get around it you need to pass UIViewAnimationOptionAllowUserInteraction as one of the options.
I fixed this problem by setting alpha to 0.1 instead of 0.0. I'm not sure if that will work in your case, but it shows that the event handling code thought that the view was not visible and disabled interaction even with the UIViewAnimationOptionAllowUserInteraction flag set. Oddly, setting the alpha to 0.01 did not work, so there is a threshold of visibility you have to stay above.
Swift 5
UIView.animateKeyframes(withDuration: 0.5, delay: 0, options: [.repeat, .autoreverse, .allowUserInteraction], animations: {
self.customButton.backgroundColor = .none
}, completion: nil)
The issue is with the Alpha value of 0. Alpha values of a certain proximity to Zero will remove the view from the view responder hierarchy. The fix here is to make the alpha setting to this:
self.cardView.alpha = 0.011
The view will still be invisible but not removed from the responder chain. From my testing the minimum amount is the following:
extension CGFloat {
static let minAlphaForTouchInput: CGFloat = 0.010000001
}