Swift UITabBarController hide with animation - ios

I'm trying to add animation to my tabBarController when hidden. Im able to accomplish this effect with the navigationBarController by using self.navigationController?.isNavigationBarHidden = true. I'm able to hide the tabBar by using self.tabBarController?.tabBar.isHidden = true but i do not get the animation how can I do this thank you in advance.

You could change the tab bar's frame inside an animation, so something like:
func hideTabBar() {
var frame = self.tabBarController?.tabBar.frame
frame?.origin.y = self.view.frame.size.height + (frame?.size.height)!
UIView.animate(withDuration: 0.5, animations: {
self.tabBarController?.tabBar.frame = frame!
})
}
func showTabBar() {
var frame = self.tabBarController?.tabBar.frame
frame?.origin.y = self.view.frame.size.height - (frame?.size.height)!
UIView.animate(withDuration: 0.5, animations: {
self.tabBarController?.tabBar.frame = frame!
})
}
Which sets the tab bar just below the visible screen, so that it slides up/down from the bottom.

I've developed a util extension for UIViewController
Swift 4 compatible:
extension UIViewController {
func setTabBarHidden(_ hidden: Bool, animated: Bool = true, duration: TimeInterval = 0.3) {
if animated {
if let frame = self.tabBarController?.tabBar.frame {
let factor: CGFloat = hidden ? 1 : -1
let y = frame.origin.y + (frame.size.height * factor)
UIView.animate(withDuration: duration, animations: {
self.tabBarController?.tabBar.frame = CGRect(x: frame.origin.x, y: y, width: frame.width, height: frame.height)
})
return
}
}
self.tabBarController?.tabBar.isHidden = hidden
}
}

Improvement of the response of #Luca Davanzo. If the bar is already hidden, it will continue hiding it and moving it lower. Also get rid of the return, so the state of the tabbar.hidden changes when the animation happens.
So I added a check:
extension UIViewController {
func setTabBarHidden(_ hidden: Bool, animated: Bool = true, duration: TimeInterval = 0.5) {
if self.tabBarController?.tabBar.isHidden != hidden{
if animated {
//Show the tabbar before the animation in case it has to appear
if (self.tabBarController?.tabBar.isHidden)!{
self.tabBarController?.tabBar.isHidden = hidden
}
if let frame = self.tabBarController?.tabBar.frame {
let factor: CGFloat = hidden ? 1 : -1
let y = frame.origin.y + (frame.size.height * factor)
UIView.animate(withDuration: duration, animations: {
self.tabBarController?.tabBar.frame = CGRect(x: frame.origin.x, y: y, width: frame.width, height: frame.height)
}) { (bool) in
//hide the tabbar after the animation in case ti has to be hidden
if (!(self.tabBarController?.tabBar.isHidden)!){
self.tabBarController?.tabBar.isHidden = hidden
}
}
}
}
}
}
}

In case if you need to toggle it from hide to visible and vice versa:
func toggleTabbar() {
guard var frame = tabBarController?.tabBar.frame else { return }
let hidden = frame.origin.y == view.frame.size.height
frame.origin.y = hidden ? view.frame.size.height - frame.size.height : view.frame.size.height
UIView.animate(withDuration: 0.3) {
self.tabBarController?.tabBar.frame = frame
}
}

Swift 4 solution:
tabBarController?.tabBar.isHidden = true
UIView.transition(with: tabBarController!.view, duration: 0.35, options: .transitionCrossDissolve, animations: nil)

Here is a simple extension :
func setTabBar(hidden:Bool) {
guard let frame = self.tabBarController?.tabBar.frame else {return }
if hidden {
UIView.animate(withDuration: 0.3, animations: {
self.tabBarController?.tabBar.frame = CGRect(x: frame.origin.x, y: frame.origin.y + frame.height, width: frame.width, height: frame.height)
})
}else {
UIView.animate(withDuration: 0.3, animations: {
self.tabBarController?.tabBar.frame = UITabBarController().tabBar.frame
})
}
}

So I've been playing around for 3 days with this now, finding out that the one that worked for me in my code was Adriana's post from 14th Sept 2018. But I was not sure how to use the coding once copied into my Project. So, after much experimenting I found that the way I could use this func was to put the following into into the respective swipe actions.
setTabBarHidden(false)
setTabBarHidden(true)
My next step is to try to get the swipe actions working while using UIScrollView in the same UIView at the same time.

You have to add UIView transitionWithView class func
Swift 2
func hideTabBarWithAnimation() -> () {
UIView.transitionWithView(tableView, duration: 1.0, options: .TransitionCrossDissolve, animations: { () -> Void in
self.tabBarController?.tabBar.hidden = true
}, completion: nil)
}
Swift 3, 4, 5
func hideTabBarWithAnimation() -> () {
UIView.transition(with: tableView, duration: 1.0, options: .transitionCrossDissolve, animations: { () -> Void in
self.tabBarController?.tabBar.isHidden = true
}, completion: nil)
}

Related

UICollectionView show hide animation issue

I am getting animation issue while hiding the UICollectionView. Show animation works fine but when I do the hide animation, it immediately hides the collection view without animation. This is the code :
#objc func openMenu(sender: UIButton) {
if sender.tag == 1 {
self.buttonView.tag = 2
self.arrow.image = UIImage(named: "arrowUp.png")
UIView.animate(withDuration: 0.7, animations: {
self.moduleView.frame.size.height = UIScreen.main.bounds.size.height - self.frame.size.height
}, completion: { _ in
})
} else {
self.buttonView.tag = 1
self.arrow.image = UIImage(named: "arrowDown.png")
UIView.animate(withDuration: 0.7, animations: {
self.moduleView.frame.size.height = 0
}, completion: { _ in
})
}
}
Output :
The strange thing is, I replaced the collection view with a simple UIView and it works fine. Bottom to top animation works perfectly. Code :
#objc func openMenu(sender: UIButton) {
if sender.tag == 1 {
self.buttonView.tag = 2
self.arrow.image = UIImage(named: "arrowUp.png")
UIView.animate(withDuration: 0.7, animations: {
self.testView.frame.size.height = UIScreen.main.bounds.size.height - self.frame.size.height
}, completion: { _ in
})
} else {
self.buttonView.tag = 1
self.arrow.image = UIImage(named: "arrowDown.png")
UIView.animate(withDuration: 0.7, animations: {
self.testView.frame.size.height = 0
}, completion: { _ in
})
}
}
Output :
Question : Why doesn't that work for UICollectionView ?
Initialisation :
UICollectionView :
self.moduleView = ModulesCollectionView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 0), collectionViewLayout: UICollectionViewLayout())
self.parentView.addSubView(self.moduleView)
UIView :
self.testView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 0))
self.parentView.addSubView(self.testView)
You need to use layoutSubViews() method for proper animation. Please change your code as below :
#objc func openMenu(sender: UIButton) {
if sender.tag == 1 {
self.buttonView.tag = 2
self.arrow.image = UIImage(named: "arrowUp.png")
UIView.animate(withDuration: 0.7, animations: {
self.moduleView.frame.size.height = UIScreen.main.bounds.size.height - self.frame.size.height
// Add this line
self.moduleView.layoutSubviews()
}, completion: { _ in
})
} else {
self.buttonView.tag = 1
self.arrow.image = UIImage(named: "arrowDown.png")
UIView.animate(withDuration: 0.7, animations: {
self.moduleView.frame.size.height = 0
// Add this line
self.moduleView.layoutSubviews()
}, completion: { _ in
})
}
}

Rotate UIBarButtonItem Swift

In Swift, I have a hamburger bar button, so when tapped I want that hamburger Bar button to rotate 90 degrees (so that the lines are vertical) and then when you click it again I would like it to go back to it's original state (horizontal)
NOTE: Can you make sure that this works for a UIBarButtonItem, because some solution to a normal UIButton does not work.
I use a UIButton inside of UIBarButtonItem to achieve this, and a variable with state vertical or not
this is my storyboard setup
Here is the code of simple view controller
import UIKit
class ViewController: UIViewController {
var isVertical : Bool = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func rotateAction(_ sender: Any) {
if(!self.isVertical)
{
UIView.animate(withDuration: 0.2, animations: {
self.navigationItem.leftBarButtonItem?.customView?.transform = CGAffineTransform(rotationAngle: 90 * .pi / 180)
}, completion: { (finished) in
self.isVertical = true
})
}else{
UIView.animate(withDuration: 0.2, animations: {
self.navigationItem.leftBarButtonItem?.customView?.transform = CGAffineTransform.identity
}, completion: { (finished) in
self.isVertical = false
})
}
}
}
Result
Hope this helps
#IBAction func rotateAction1(_ sender: Any) {
if (!self.isVertical) {
UIView.animate(withDuration: 0.2, animations: {
self.navigationItem.leftBarButtonItem?.customView?.transform = CGAffineTransform(rotationAngle: 90 * .pi / 180)
}, completion: {
(finished) in
self.isVertical = true
})
revealViewController().revealToggle(true)
} else {
UIView.animate(withDuration: 0.2, animations: {
self.navigationItem.leftBarButtonItem?.customView?.transform = CGAffineTransform.identity
}, completion: {
(finished) in
self.isVertical = false
})
revealViewController().revealToggle(false)
}
}
Swift 4:
func rotateBarButton() {
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 40, height: 40)) // Create new button & set its frame
button.setImage(#imageLiteral(resourceName: "settings"), for: UIControlState()) // Assign an image
let lef = UIBarButtonItem(customView: button)
self.navigationItem.leftBarButtonItem = lef// Set as barButton's customView
// Gets you half way there //
UIView.animate(withDuration: 0.8, delay: 0.1, options: UIViewAnimationOptions.curveEaseIn, animations: {
self.navigationItem.leftBarButtonItem?.customView?.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI))
}, completion: nil)
// Rotates all the way around //
UIView.animate(withDuration: 0.5, delay: 0.5, options: UIViewAnimationOptions.curveEaseIn, animations: {
self.navigationItem.leftBarButtonItem?.customView?.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI * 2))
}, completion: nil)
}

ios - animate button to shrink in width from the right

I am using CGAffineTransform to shrink a button on click. The button shrinks in width but from the left and right meeting in the middle. However, I want the button to shrink from the right. A weird analogy is like the button is a piece of paper, and I lit the right side with a match and it burns the paper, destroying it towards the left. How would I achieve such functionality?
func animateStartButtonExit() {
UIView.animate(withDuration: 0.5,
animations: {
self.startButtonOutlet.transform = CGAffineTransform(scaleX: 0.1, y: 1)
},
completion: { _ in
UIView.animate(withDuration: 0.5) {
self.startButtonOutlet.isHidden = true
self.startButtonOutlet.transform = CGAffineTransform.identity
}
})
}
It works fine. I have just tested.
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton!
#IBOutlet weak var widthConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func click(_ sender: UIButton) {
widthConstraint.constant = 10
UIView.animate(withDuration: 5, animations: {
self.view.layoutIfNeeded()
}, completion: { _ in
self.button.isHidden = true
// For example, back to the normal width
self.widthConstraint.constant = 168
})
}
}
You can try this which is animate the button from Right,
UIView.animate(withDuration: 5,
animations: {
button.frame = CGRect(x: button.frame.minX, y: button.frame.minY, width: button.frame.size.width / 2, height: button.frame.size.height / 2)
},
completion: { _ in
UIView.animate(withDuration: 0.5) {
button.isHidden = true
button.frame = CGRect(x: button.frame.minX, y: button.frame.minY, width: button.frame.size.width * 2, height: button.frame.size.height * 2)
}
})
If you are using autolayout, set the trailing space constraint to add up to make the width 0.
ie
rightConstraint.constant = rightConstraint.constant + width
UIView.animate(withDuration: 5,
animations: {
button.layoutIfNeeded()
},
completion: nil
)

Stop block of animations with UITapGestureRecognizer

I have got this problem with breaking animations block in my app. I am animating an image inside UIScrollView (zooming-in, moving from left to right, zooming-out). Animation is being launched on tap with UITapGestureRecognizer.
The problem is that even though I can zoom-out the image when it is being animated, animations block continues and as a result I get the image off the iPhone screen.
I have tried to use removeAllAnimations() method on both scrollView.layer and view.layer but it seems not to work.
Here is my code:
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
setupGestureRecognizer()
}
func setupGestureRecognizer() {
let singleTap = UITapGestureRecognizer(target: self, action: #selector(self.handleSingleTap(recognizer:)))
singleTap.numberOfTapsRequired = 1
scrollView.addGestureRecognizer(singleTap)
}
func handleSingleTap(recognizer: UITapGestureRecognizer) {
animateRecipeImage()
}
// MARK: - Recipe Image Animation
func animateRecipeImage() {
let offset = CGPoint(x: 0, y: 0)
var middleOffset = CGPoint()
let endOffset = CGPoint(x: (imageView.bounds.width / 2), y: 0)
// To avoid animation in landscape mode we check for current device orientation
if (UIDeviceOrientationIsPortrait(UIDevice.current.orientation)) {
if (scrollView.zoomScale > scrollView.minimumZoomScale) {
scrollView.setZoomScale(scrollView.minimumZoomScale, animated: true)
// Remove all animations and update image contraints to fit the screen
self.scrollView.layer.removeAllAnimations()
self.view.layer.removeAllAnimations()
updateConstraintsForSize(size: view.bounds.size)
} else {
scrollView.setZoomScale(scrollView.maximumZoomScale, animated: true)
let screenWidth = screenSize.width
// Handle big screen sizes
if (screenWidth < self.imageView.bounds.width) {
middleOffset = CGPoint(x: ((self.imageView.bounds.width - screenWidth) * self.scrollView.zoomScale), y: 0)
} else {
middleOffset = CGPoint(x: (self.imageView.frame.width - screenWidth), y: 0)
}
// Start animating the image
UIView.animate(withDuration: 2, delay: 0.5, options: [.allowUserInteraction], animations: {
self.scrollView.setContentOffset(offset, animated: false)
}, completion: { _ in
UIView.animate(withDuration: 4, delay: 0.5, options: [.allowUserInteraction], animations: {
self.scrollView.setContentOffset(middleOffset, animated: false)
}, completion: { _ in
UIView.animate(withDuration: 1.0, delay: 0.2, usingSpringWithDamping: 0.4, initialSpringVelocity: 1.1, options: [.allowUserInteraction], animations: {
self.scrollView.setContentOffset(endOffset, animated: false)
}, completion: nil)
})
})
}
}
}
Any ideas how to break that animations block? The result I would like to achieve is break animation and zoom-out image.

How do I hide/show tabBar when tapped using Swift in iOS8

I am trying to mimic the UINavigationController's new hidesBarsOnTap with a tab bar. I have seen many answers to this that either point to setting the hidesBottomBarWhenPushed on a viewController which only hides it entirely and not when tapped.
#IBAction func tapped(sender: AnyObject) {
// what goes here to show/hide the tabBar ???
}
thanks in advance
EDIT: as per the suggestion below I tried
self.tabBarController?.tabBar.hidden = true
which does indeed hide the tabBar (toggles true/false on tap), but without animation. I will ask that as a separate question though.
After much hunting and trying out various methods to gracefully hide/show the UITabBar using Swift I was able to take this great solution by danh and convert it to Swift:
func setTabBarVisible(visible: Bool, animated: Bool) {
//* This cannot be called before viewDidLayoutSubviews(), because the frame is not set before this time
// bail if the current state matches the desired state
if (tabBarIsVisible() == visible) { return }
// get a frame calculation ready
let frame = self.tabBarController?.tabBar.frame
let height = frame?.size.height
let offsetY = (visible ? -height! : height)
// zero duration means no animation
let duration: TimeInterval = (animated ? 0.3 : 0.0)
// animate the tabBar
if frame != nil {
UIView.animate(withDuration: duration) {
self.tabBarController?.tabBar.frame = frame!.offsetBy(dx: 0, dy: offsetY!)
return
}
}
}
func tabBarIsVisible() -> Bool {
return (self.tabBarController?.tabBar.frame.origin.y)! < self.view.frame.maxY
}
// Call the function from tap gesture recognizer added to your view (or button)
#IBAction func tapped(_ sender: Any?) {
setTabBarVisible(visible: !tabBarIsVisible(), animated: true)
}
Loved Michael Campsall's answer. Here's the same code as extension, if somebody is interested:
Swift 2.3
extension UITabBarController {
func setTabBarVisible(visible:Bool, animated:Bool) {
// bail if the current state matches the desired state
if (tabBarIsVisible() == visible) { return }
// get a frame calculation ready
let frame = self.tabBar.frame
let height = frame.size.height
let offsetY = (visible ? -height : height)
// animate the tabBar
UIView.animateWithDuration(animated ? 0.3 : 0.0) {
self.tabBar.frame = CGRectOffset(frame, 0, offsetY)
self.view.frame = CGRectMake(0, 0, self.view.frame.width, self.view.frame.height + offsetY)
self.view.setNeedsDisplay()
self.view.layoutIfNeeded()
}
}
func tabBarIsVisible() ->Bool {
return self.tabBar.frame.origin.y < CGRectGetMaxY(self.view.frame)
}
}
Swift 3
extension UIViewController {
func setTabBarVisible(visible: Bool, animated: Bool) {
//* This cannot be called before viewDidLayoutSubviews(), because the frame is not set before this time
// bail if the current state matches the desired state
if (isTabBarVisible == visible) { return }
// get a frame calculation ready
let frame = self.tabBarController?.tabBar.frame
let height = frame?.size.height
let offsetY = (visible ? -height! : height)
// zero duration means no animation
let duration: TimeInterval = (animated ? 0.3 : 0.0)
// animate the tabBar
if frame != nil {
UIView.animate(withDuration: duration) {
self.tabBarController?.tabBar.frame = frame!.offsetBy(dx: 0, dy: offsetY!)
return
}
}
}
var isTabBarVisible: Bool {
return (self.tabBarController?.tabBar.frame.origin.y ?? 0) < self.view.frame.maxY
}
}
I had to adapt the accepted answer to this question a bit. It was hiding the bar but my view wasn't sizing itself appropriately so I was left with a space at the bottom.
The following code successfully animates the hiding of the tab bar while resizing the view to avoid that issue.
Updated for Swift 3 (now with less ugly code)
func setTabBarVisible(visible: Bool, animated: Bool) {
guard let frame = self.tabBarController?.tabBar.frame else { return }
let height = frame.size.height
let offsetY = (visible ? -height : height)
let duration: TimeInterval = (animated ? 0.3 : 0.0)
UIView.animate(withDuration: duration,
delay: 0.0,
options: UIViewAnimationOptions.curveEaseIn,
animations: { [weak self] () -> Void in
guard let weakSelf = self else { return }
weakSelf.tabBarController?.tabBar.frame = frame.offsetBy(dx: 0, dy: offsetY)
weakSelf.view.frame = CGRect(x: 0, y: 0, width: weakSelf.view.frame.width, height: weakSelf.view.frame.height + offsetY)
weakSelf.view.setNeedsDisplay()
weakSelf.view.layoutIfNeeded()
})
}
func handleTap(recognizer: UITapGestureRecognizer) {
setTabBarVisible(visible: !tabBarIsVisible(), animated: true)
}
func tabBarIsVisible() -> Bool {
guard let tabBar = tabBarController?.tabBar else { return false }
return tabBar.frame.origin.y < UIScreen.main.bounds.height
}
Older Swift 2 Version
func setTabBarVisible(visible: Bool, animated: Bool) {
// hide tab bar
let frame = self.tabBarController?.tabBar.frame
let height = frame?.size.height
var offsetY = (visible ? -height! : height)
println ("offsetY = \(offsetY)")
// zero duration means no animation
let duration:NSTimeInterval = (animated ? 0.3 : 0.0)
// animate tabBar
if frame != nil {
UIView.animateWithDuration(duration) {
self.tabBarController?.tabBar.frame = CGRectOffset(frame!, 0, offsetY!)
self.view.frame = CGRectMake(0, 0, self.view.frame.width, self.view.frame.height + offsetY!)
self.view.setNeedsDisplay()
self.view.layoutIfNeeded()
return
}
}
}
#IBAction func handleTap(recognizer: UITapGestureRecognizer) {
setTabBarVisible(!tabBarIsVisible(), animated: true)
}
func tabBarIsVisible() -> Bool {
return self.tabBarController?.tabBar.frame.origin.y < UIScreen.mainScreen().bounds.height
}
You can just add this line to ViewDidLoad() in swift :
self.tabBarController?.tabBar.hidden = true
I use tabBar.hidden = YES in ObjC to hide the tab bar in certain cases. I have not tried wiring it up to a tap event, though.
Code is okay but when you use presentViewController, tabBarIsVisible() is not working. To keep UITabBarController always hidden use just this part:
extension UITabBarController {
func setTabBarVisible(visible:Bool, animated:Bool) {
let frame = self.tabBar.frame
let height = frame.size.height
let offsetY = (visible ? -height : height)
UIView.animateWithDuration(animated ? 0.3 : 0.0) {
self.tabBar.frame = CGRectOffset(frame, 0, offsetY)
self.view.frame = CGRectMake(0, 0, self.view.frame.width, self.view.frame.height + offsetY)
self.view.setNeedsDisplay()
self.view.layoutIfNeeded()
}
}
}
Swift 3 version:
func setTabBarVisible(visible:Bool, animated:Bool) {
//* This cannot be called before viewDidLayoutSubviews(), because the frame is not set before this time
// bail if the current state matches the desired state
if (tabBarIsVisible() == visible) { return }
// get a frame calculation ready
let frame = self.tabBarController?.tabBar.frame
let height = frame?.size.height
let offsetY = (visible ? -height! : height)
// zero duration means no animation
let duration:TimeInterval = (animated ? 0.3 : 0.0)
// animate the tabBar
if frame != nil {
UIView.animate(withDuration: duration) {
self.tabBarController?.tabBar.frame = (self.tabBarController?.tabBar.frame.offsetBy(dx: 0, dy: offsetY!))!
return
}
}
}
func tabBarIsVisible() ->Bool {
return (self.tabBarController?.tabBar.frame.origin.y)! < self.view.frame.midY
}
Swift 5
To hide
override func viewWillAppear(_ animated: Bool) {
self.tabBarController?.tabBar.isHidden = true
}
To show again
override func viewDidDisappear(_ animated: Bool) {
self.tabBarController?.tabBar.isHidden = false
}
For Swift 4, and animating + hiding by placing tabBar outside the view:
if let tabBar = tabBarController?.tabBar,
let y = tabBar.frame.origin.y + tabBar.frame.height {
UIView.animate(withDuration: 0.2) {
tabBar.frame = CGRect(origin: CGPoint(x: tabBar.frame.origin.x, y: y), size: tabBar.frame.size)
}
}
To make the animations work with self.tabBarController?.tabBar.hidden = true just do this:
UIView.animateWithDuration(0.2, animations: {
self.tabBarController?.tabBar.hidden = true
})
Other than the other solution this will also work nicely with autolayout.

Resources