I have a UIView EmptyCollectionView, which I display when my UICollectionView is empty. The way I have this working is that I create the UIView and addSubview in viewDidLoad of my ViewController, then change toggle isHidden property of the view (as well as the collectionview) as needed.
I'd like to polish things up a little now I have the core function working, and I wan't to add some subtle animation to the subviews contained in my empty view, such as making the contained imageview bounce on display.
So my question is, what is the best way to detect when the UIView is being shown (i.e. is there a viewDidAppear type callback I could use)?
Supplementary question: I'm new to this... Is adding the empty view and toggling the isHidden property a good way of doing this? Or should I be doing it a different way? (i.e. should I instead be creating and destroying the view as needed, rather than keeping it around)
Thanks
The best way in my opinion is to extend UIView
extension UIView {
func fadeIn(_ duration: TimeInterval = 0.2, onCompletion: (() -> Void)? = nil) {
self.alpha = 0
self.isHidden = false
UIView.animate(withDuration: duration,
animations: { self.alpha = 1 },
completion: { (value: Bool) in
if let complete = onCompletion { complete() }
}
)
}
func fadeOut(_ duration: TimeInterval = 0.2, onCompletion: (() -> Void)? = nil) {
UIView.animate(withDuration: duration,
animations: { self.alpha = 0 },
completion: { (value: Bool) in
self.isHidden = true
if let complete = onCompletion { complete() }
}
)
}
}
So you just have to call view.fadeIn() for a default 0.2 sec animation, or view.fadeIn(1) to make it last one second.
You can even add a completion event:
view.fadeOut(0.5, onCompletion: {
print("Animation completed, do whatever you want")
})
This works, I hope it can help you. To hide view:
UIView.animate(withDuration: 0.3/*Animation Duration second*/, animations: {
self.EmptyCollectionView.alpha = 0
}, completion: {
(value: Bool) in
self.EmptyCollectionView.isHidden = true
})
To show view:
self.EmptyCollectionView.isHidden = false
UIView.animate(withDuration: 0.3, animations: {
self.EmptyCollectionView.alpha = 1
}, completion: nil)
Swift 4.2 extension to allow animation when setting isHidden on any UIView class:
extension UIView {
func setIsHidden(_ hidden: Bool, animated: Bool) {
if animated {
if self.isHidden && !hidden {
self.alpha = 0.0
self.isHidden = false
}
UIView.animate(withDuration: 0.25, animations: {
self.alpha = hidden ? 0.0 : 1.0
}) { (complete) in
self.isHidden = hidden
}
} else {
self.isHidden = hidden
}
}
}
You can animate the alpha property of EmptyCollectionView to either 0 to hide or 1 to show
UIView.animate(withDuration: 0.5) {
self.EmptyCollectionView.alpha = 0
}
Also make sure that isOpaque property is set to False to enable Transparency of the view
Swift 5
import UIKit
extension UIView {
func animateSetHidden(_ hidden: Bool, duration: CGFloat = CATransaction.animationDuration(), completion: #escaping (Bool)->() = { _ in}) {
if duration > 0 {
if self.isHidden && !hidden {
self.alpha = 0
self.isHidden = false
}
UIView.animate(withDuration: duration, delay:0, options: .beginFromCurrentState) {
self.alpha = hidden ? 0 : 1
} completion: { c in
if c {
self.isHidden = hidden
}
completion(c)
}
} else {
self.isHidden = hidden
self.alpha = hidden ? 0 : 1
completion(true)
}
}
}
Related
I have I'm trying to using fade in and fade out transition when any controller present and dismiss. For this, when new controller present I set alpha value of all the component/asset of the view like label, buttons etc to Zero in viewDidLoad and view didAppear I set back the alpha below to 1.
Below is the code I'm using:
override func viewDidLoad() {
super.viewDidLoad()
imageView1.isHidden = true
imageView2.isHidden = true
self.setupScreen()
setAssetsAlphaZero()
}
private func setupScreen() {
switch loginVM.loginScreenType {
case .resetPassword:
presentResetPasswordScreen()
default: break
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
animateAssetsWithIdentity()
}
func animateAssetsWithIdentity() {
self.view.layoutIfNeeded()
self.view.setNeedsDisplay()
hideShowAssets(hidden: false)
UIView.animate(withDuration: 0.5, animations: { [weak self] in
self?.passwordTextField.alpha = 1
self?.forgotPasswordButton.alpha = 1
self?.emailTextField.alpha = 1
self?.loginButton.alpha = 1
self?.loginLbl.alpha = 1
self?.emailImg.alpha = 1
self?.emailLbl.alpha = 1
self?.passwordImg.alpha = 1
self?.passwordLbl.alpha = 1
self?.signUpBtn.alpha = 1
self?.view.layoutIfNeeded()
self?.view.setNeedsDisplay()
}) {[weak self] (complete) in
self?.signUpBtn.isEnabled = true
self?.view.layoutIfNeeded()
self?.view.setNeedsDisplay()
}
self.enableDisableButton()
}
func setAssetsAlphaZero() {
self.view.layoutIfNeeded()
self.view.setNeedsDisplay()
UIView.animate(withDuration: 0.5, animations: {[weak self] in
self?.passwordTextField.alpha = 0
self?.forgotPasswordButton.alpha = 0
self?.emailTextField.alpha = 0
self?.loginButton.alpha = 0
self?.loginLbl.alpha = 0
self?.emailImg.alpha = 0
self?.emailLbl.alpha = 0
self?.passwordImg.alpha = 0
self?.passwordLbl.alpha = 0
self?.signUpBtn.alpha = 0
self?.view.layoutIfNeeded()
self?.view.setNeedsDisplay()
}) {[weak self] (complete) in
self?.hideShowAssets(hidden: true)
self?.signUpBtn.isEnabled = false
self?.view.layoutIfNeeded()
self?.view.setNeedsDisplay()
}
}
func hideShowAssets(hidden : Bool ) {
self.passwordTextField.isHidden = hidden
self.forgotPasswordButton.isHidden = hidden
self.emailTextField.isHidden = hidden
self.loginButton.isHidden = hidden
self.loginLbl.isHidden = hidden
self.emailImg.isHidden = hidden
self.emailLbl.isHidden = hidden
self.passwordImg.isHidden = hidden
self.passwordLbl.isHidden = hidden
self.signUpBtn.isHidden = hidden
}
And when the controller dismiss than with help of delegate i notify the previous controller.
#IBAction func signUpButtonTapped(_ sender: UIButton) {
setAssetsAlphaZero()
self.delegate?.dissmissVC()
delay(0.5) {[weak self] in
self?.dismiss(animated: false, completion: nil)
}
}
To present the controller I used modalPresentationStyle = .overFullScreen because I need the present we controller should be transparent.
I don't think you need to call setNeedsDisplay. Because it will react to function drawRect as mention here. Also some suggestions, i think it's better to call setAssetsAlphaZero() then add delay 0.5 sec then call animateAssetsWithIdentity() . Hopefully this is helpful
When do I need to call setNeedsDisplay in iOS?
I want to show the View for a few seconds and make it disappear from the screen.
If I wait 10 seconds on this screen
I want to make this view go off screen.
How can I animate the view slowly descending down?
code...
fileprivate func setupViewToAnimate(view: UIView) {
UIView.animate(withDuration: 2, delay: 10, options: .curveEaseInOut , animations: {
}) { _ in
}
}
You can use Timer to schedule the dismiss of your snackBar/notification view. it will be triggered after the completion of animation for showing your view. Please check the following code for the same.
private let keyWindow = UIApplication.shared.keyWindow!
let snackbarView = UIView()
fileprivate func setupViewToAnimate() {
let targetYPost = snackbarView.frame.origin.y
let dismissTimerLength = 3.0
snackbarView.frame.origin.y = keyWindow.frame.height
UIView.animate(withDuration: 0.4, animations: {
self.snackbarView.frame.origin.y = targetYPost
}, completion: {
_ in
Timer.scheduledTimer(timeInterval: TimeInterval(dismissTimerLength), target: self, selector: #selector(self.hideSnackbarView), userInfo: nil, repeats: false)
})
}
//After 3.4 sec snack bar will be hidden
#objc private func hideSnackbarView() {
UIView.animate(withDuration: 0.4, animations: {
self.snackbarView.frame.origin.y = self.keyWindow.frame.height
}, completion: {
_ in
self.snackbarView.isHidden = true
})
}
Output:-
I would suggest something like this:
private func setupViewToAnimate(view: UIView, bottomConstraint: NSLayoutConstraint) {
UIView.animate(withDuration: 2, delay: 10, options: .curveEaseInOut , animations: {
// Set the bottomConstraint to minus the height of the view so it gets entierly hidden
bottomConstraint.constant = -view.frame.height
// Tell the view to re-layout its subviews again so we have something to animate
self.view.layoutIfNeeded()
}) { _ in
// If the animation is completed, we can remove the view from the superview since we don't need it anymore
view.removeFromSuperview()
}
}
override func viewDidLoad() {
let tap = UITapGestureRecognizer(target: self, action: #selector(touchHandled))
view.addGestureRecognizer(tap)
}
#objc func touchHandled() {
tabBarController?.hideTabBarAnimated(hide: true)
}
extension UITabBarController {
func hideTabBarAnimated(hide:Bool) {
UIView.animate(withDuration: 2, animations: {
if hide {
self.tabBar.transform = CGAffineTransform(translationX: 0, y: 100)
} else {
self.tabBar.transform = CGAffineTransform(translationX: 0, y: -100)
}
})
}
}
I can only hide the tab bar but I can't make it show when you tap again. I tried to look for answers on stack overflow but the answers seems to only work if you're using a button or a storyboard.
Have a variable isTabBarHidden in class which stores if the tabBar has been animated to hide. (You could have used tabBar.isHidden, but that would complicate the logic a little bit when animate hiding and showing)
class ViewController {
var isTabBarHidden = false // set the default value as required
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(touchHandled))
view.addGestureRecognizer(tap)
}
#objc func touchHandled() {
guard let tabBarControllerFound = tabBarController else {
return
}
tabBarController?.hideTabBarAnimated(hide: !isTabBarHidden)
isTabBarHidden = !isTabBarHidden
}
}
Generalised solution with protocol which will work in all the screens
Create UIViewController named BaseViewController and make it base class of all of your view controllers
Now Define protocol
protocol ProtocolHideTabbar:class {
func hideTabbar ()
}
protocol ProtocolShowTabbar:class {
func showTabbar ()
}
extension ProtocolHideTabbar where Self : UIViewController {
func hideTabbar () {
self.tabBarController?.tabBar.isHidden = true
}
}
extension ProtocolShowTabbar where Self : UIViewController {
func showTabbar () {
self.tabBarController?.tabBar.isHidden = false
}
}
By default we want show tabbar in every view controller so
extension UIViewController : ProtocolShowTabbar {}
In your BaseView Controller
in view will appear method add following code to show hide based on protocol
if self is ProtocolHideTabbar {
( self as! ProtocolHideTabbar).hideTabbar()
} else if self is ProtocolShowTabbar{
( self as ProtocolShowTabbar).showTabbar()
}
How to use
Simply
class YourViewControllerWithTabBarHidden:BaseViewController,ProtocolHideTabbar {
}
Hope it is helpful
Tested 100% working
Please try below code for that in UITabBarController subclass
var isTabBarHidden:Bool = false
func setTabBarHidden(_ tabBarHidden: Bool, animated: Bool,completion:((Void) -> Void)? = nil) {
if tabBarHidden == isTabBarHidden {
self.view.setNeedsDisplay()
self.view.layoutIfNeeded()
//check tab bar is visible and view and window height is same then it should be 49 + window Heigth
if (tabBarHidden == true && UIScreen.main.bounds.height == self.view.frame.height) {
let offset = self.tabBar.frame.size.height
self.view.frame = CGRect(x:0, y:0, width:self.view.frame.width, height:self.view.frame.height + offset)
}
if let block = completion {
block()
}
return
}
let offset: CGFloat? = tabBarHidden ? self.tabBar.frame.size.height : -self.tabBar.frame.size.height
UIView.animate(withDuration: animated ? 0.250 : 0.0, delay: 0.1, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.5, options: [.curveEaseIn, .layoutSubviews], animations: {() -> Void in
self.tabBar.center = CGPoint(x: CGFloat(self.tabBar.center.x), y: CGFloat(self.tabBar.center.y + offset!))
//Check if View is already at bottom so we don't want to move view more up (it will show black screen on bottom ) Scnario : When present mail app
if (Int(offset!) <= 0 && UIScreen.main.bounds.height == self.view.frame.height) == false {
self.view.frame = CGRect(x:0, y:0, width:self.view.frame.width, height:self.view.frame.height + offset!)
}
self.view.setNeedsDisplay()
self.view.layoutIfNeeded()
}, completion: { _ in
if let block = completion {
block()
}
})
isTabBarHidden = tabBarHidden
}
Hope it is helpful
I want to perform some animation on secondTextField when the FirstTextfield becomesFirstResponder
After resign first responder the animation should reverse back in second text field. How can I achieve this in swift?
Note: It is different from moving textfield to the top on keyboard appears.
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
if(textField == firstTextField) {
UIView.animateWithDuration(1.0, animations: {
self.secTextField.frame = CGRectMake(self.secTextField.frame.origin.x + 500, self.secTextField.frame.origin.y, self.secTextField.frame.size.width, self.secTextField.frame.size.height) })
}
return true
}
What I want to do is, when the user types in firstTextField, the secTextField should go and hide out of sight.
What actually happens is the secTextField comes from out of sight to its original position
You can use the delegate methods of UITextField to achieve this. If you explain more about your animation I can help you with that also.
func textFieldDidBeginEditing(textField: UITextField) {
if textField == self.firstTextField {
UIView.animateWithDuration(2, delay: 0, options: nil, animations: { () -> Void in
self.secondTextField.frame = CGRectOffset(self.secondTextField.frame, 500, 0)
}, completion: nil)
}
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
if textField == self.firstTextField {
UIView.animateWithDuration(2, delay: 0, options: nil, animations: { () -> Void in
self.secondTextField.frame = CGRectOffset(self.secondTextField.frame, -500, 0)
}, completion: nil)
}
textField.resignFirstResponder()
return true
}
Don't forget to set delegates on the UITextField objects (on your viewDidLoad)
self.firstTextField.delegate = self
self.secondTextField.delegate = self
You can download the example from here: https://github.com/alextarrago/text-field-test
In my case it works for me. Just use self.view.layoutIfNeeded()
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
if(textField == firstTextField) {
UIView.animateWithDuration(1.0, animations: {
self.secTextField.frame = CGRectMake(self.secTextField.frame.origin.x + 500, self.secTextField.frame.origin.y, self.secTextField.frame.size.width, self.secTextField.frame.size.height)
self.view.layoutIfNeeded()
})
}
return true
}
I finally found the answer.
I did the following steps.
Clean My project,
Enable and then again disable the Auto-Layouts.
Set the delegate for those textfields both in xib and swift file.
Then the coding:
`self.secTextField.frame.origin.x = self.secTextField.frame.origin.x + 500`
I am trying to animate a UIView on button click.
It does appear on first click but only after that.
#IBAction func viewDetails(sender: AnyObject) {
UIView.animateWithDuration(0.5 as NSTimeInterval, animations: {
println(self.viewDetailsView.center.x)
println(self.viewDetailsView.hidden)
self.viewDetailsView.hidden = false
self.viewDetailsView.center = CGPointMake(self.viewDetailsView.center.x - 4000, self.viewDetailsView.center.y)
self.view.layoutIfNeeded()
}, completion: {
finished in
self.detailsVisible = true
}) }
Trying this:
#IBAction func viewDetails(sender: AnyObject) {
println(self.viewDetailsView.center.x)
println(self.viewDetailsView.hidden)
self.viewDetailsView.hidden = false
self.viewDetailsView.center = CGPointMake(self.viewDetailsView.center.x - 4000, self.viewDetailsView.center.y)
UIView.animateWithDuration(0.5 as NSTimeInterval, animations: {
self.view.layoutIfNeeded()
}, completion: { finished in
self.detailsVisible = true
})
}