I use subclass of UIPresentationController to present some controller on the screen. This is how I prepare it:
controller.transitioningDelegate = self
controller.modalPresentationStyle = .Custom
presentViewController(controller, animated: true, completion: nil)
But within controller there is a textField, and I have added there observer for UIKeyboardDidShowNotification. Is it possible to update view's frame when keyboard appear?
This is how it looks like:
I need to change the bounds of that view because of keyboard.
containerView?.setNeedsLayout() needs to be called after whatever I change.
It is relative easy.
First you will need to observe for keyboard changes in your presenter
Listen to notifications .UIKeyboardWillShow, .UIKeyboardDidShow, .UIKeyboardWillHide, .UIKeyboardDidHide
I would recommend making a KeyboardObserver class for this with in example a static instance and store the keyboard variables (frame, animation speed etc) in there and add a delegate on that class to inform you on keyboard changes.
You then end up with something like this
extension PresentationController: KeyboardManagerDelegate {
internal func keyboardManager(_ manager: KeyboardManager, action: KeyboardManager.KeyBoardDisplayAction, info: KeyboardManager.Info) {
guard let containerView = containerView else { return }
UIView.animate(withDuration: info.animationDuration, delay: 0, options: info.animationOptions, animations: {
containerView.setNeedsLayout()
containerView.layoutIfNeeded()
}, completion: nil)
}
}
Next you will need to override frameOfPresentedViewInContainerView.
Example:
override var frameOfPresentedViewInContainerView: CGRect {
guard let containerView = containerView else {
return .zero
}
let desiredSize = CGSize(width: 540, height: 620)
let width = min(desiredSize.width, containerView.width)
let x = round((containerView.width - width) / 2)
if KeyboardManager.shared.isKeyboardVisible {
let availableHeight = containerView.height - KeyboardManager.shared.keyboardFrame.height
let height = availableHeight - 40
return CGRect(x: x, y: 25, width: width, height: height)
} else {
let height = min(desiredSize.height, containerView.height)
let y = round((containerView.height - height) / 2)
return CGRect(x: x, y: y, width: width, height: height)
}
}
At the end also implement a layout method to update the view
override func containerViewWillLayoutSubviews() {
super.containerViewWillLayoutSubviews()
presentedView?.frame = frameOfPresentedViewInContainerView
}
Related
I'm getting problem in size while adding child view controller
XIB veiw :
class StoreDetailView: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
func addChild(vc: UIViewController){
let v = StoreDetailView(nibName: "StoreDetailView", bundle: nil)
v.view.frame = CGRect(x: 0, y: 0, width: vc.view.frame.width, height: 80)
appendAbleView.addSubview(v.view)
vc.addChild(v)
heightOfAppendAbleView.constant = 80
_scrollView.updateContentView()
}
Output what i'm getting:
Height is not working here :
v.view.frame = CGRect(x: 0, y: 0, width: vc.view.frame.width, height: 80)
i need to show it in grey view which height 80 heightOfAppendAbleView.constant = 80
Only addChild is not enough. Let's try this extension
extension UIViewController {
// Add a child view controller, its whole view is embeded in the containerView
public func addController(controller: UIViewController, containerView: UIView) {
if let parent = controller.parent, parent == self {
return
}
addChild(controller)
controller.view.frame = CGRect.init(origin: .zero, size: containerView.frame.size)
controller.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
containerView.addSubview(controller.view)
controller.didMove(toParent: self)
}
}
Introduction
Context:
In my main ViewController I have a scrollView with a few objects inside (which are UIViews). When one of the UIViews are tapped/selected I animate forward a UITextView in a UIView to go with the selected object. (only one UIView can appear at a time)
This UIView that appears on object selection is separated into a separate class called AdjunctiveTextView.
Issue/goal:
(the example code provided below will clear make this clear, I've also commented where the issue lies in the code)
When an object has been tapped and has an adjacent UIView with a text I want to have that adjacent UIView to follow with the scrollView.
I'm using a UIPanGestureRecognizer to attempt to do this. But I can't figure out how to make it work when the user drags in the scrollview. It only work if the user drags on the actual adjunctiveTextView.
Everything works as expected except that the adjunctiveTextView does not change its position during the panGesture.
I would like (if possible) to have the AdjunctiveTextView as a separate class. My ViewController file is getting rather big.
Question:
Why doesn't the UIPanGestureRecognizer work as expected? What is needed in order for it to translate the backView correctly?
Code
My attempt: (as shown below)
My attempt simply makes the backView itself "dragable" around through the panGesture. Nothing happens to it when I scroll the scrollView.
(I have only included relevant portions of my code)
class ViewController: UIViewController {
let adjunctiveTextView = AdjunctiveTextView()
// this is a delegate method which gets called when an object is tapped in the scrollView
func scrollViewObjectIsTapped(_ objectScrollView: ObjectScrollView, object: AvailableObject) {
** adjunctiveTextView.scrollView = scrollView // **Edited! (scrollView is the name of the scrollView in this class too)
adjunctiveTextView.showView(passInObject: AvailableObject)
}
}
class AdjunctiveTextView: NSObject {
lazy var backView: UIView = {
//backView setup
}
lazy var textView: UITextView = {
//textView setup
}
//additional init and setup
** weak var scrollView : UIScrollView! // **Edited!
func showView(passInObject: AvailableObject) {
if let window = UIApplication.shared.keyWindow {
// the issue must either be here in the PanGesture setup
let panG = UIPanGestureRecognizer(target: self, action: #selector(translateView(sender:)))
panG.cancelsTouchesInView = false
// window.addGestureRecognizer(panG)
** scrollView.addGestureRecognizer(panG) // **Edited!
window.addSubview(backView)
textView.text = passInObject.information
backView.frame = CGRect(x: passInObject.frame.minX, y: passInObject.minY, width: window.frame.width - passInObject.maxX - 6, height: textView.bounds.height + 5)
backView.alpha = 0
//it animates a change of the backViews x position and alpha.
UIView.animate(withDuration: 0.42, delay: 0, options: .curveEaseInOut, animations: {
self.backView.alpha = 1
self.backView.frame = CGRect(x: passInObject.frame.minX + passInObject.frame.width, y: passInObject.minY, width: window.frame.width - passInObject.maxX - 6, height: textView.bounds.height + 5)
}, completion: nil)
}
}
// or the issue is here in the handle function for the PanGesture.
#objc private func translateView(sender: UIPanGestureRecognizer) {
if let window = UIApplication.shared.keyWindow {
let translation = sender.translation(in: window) //Have tried setting this to scrollView also
switch sender.state {
case .began, .changed:
backView.center = CGPoint(x: backView.center.x, y: backView.center.y + translation.y)
sender.setTranslation(CGPoint.zero, in: window) //Have tried setting this to sccrollView also
break
case .ended:
break
default:
break
}
}
}
}
Thanks for reading my question.
I just add a weak reference to your scrollView and then add the pan gesture to scrollView. It works as you want. You may consider add another pan gesture to the back view if you want your original behavior.
class AdjunctiveTextView: NSObject {
lazy var backView: UIView = {
//backView setup
return UIView.init()
}()
lazy var textView: UITextView = {
//textView setup
return UITextView.init(frame: CGRect.init(x: 0, y: 0, width: 300, height: 100))
}()
weak var scrollView: UIScrollView!
//additional init and setup
func showView(passInObject: AvailableObject) {
if let window = UIApplication.shared.keyWindow {
// the issue must either be here in the PanGesture setup
let panG = UIPanGestureRecognizer(target: self, action: #selector(translateView(sender:)))
panG.cancelsTouchesInView = false
// passInObject.addGestureRecognizer(panG)
scrollView.addGestureRecognizer(panG)
window.addSubview(backView)
textView.text = passInObject.information
textView.backgroundColor = UIColor.blue
backView.addSubview(textView)
backView.frame = CGRect(x: passInObject.frame.minX, y: passInObject.frame.minY, width: window.frame.width - passInObject.frame.maxX - 6, height: textView.bounds.height + 5)
backView.alpha = 0
//it animates a change of the backViews x position and alpha.
UIView.animate(withDuration: 0.42, delay: 0, options: .curveEaseInOut, animations: {
self.backView.alpha = 1
self.backView.frame = CGRect(x: passInObject.frame.minX + passInObject.frame.width , y: passInObject.frame.minY , width: window.frame.width - passInObject.frame.maxX - 6, height: self.textView.bounds.height + 5)
self.backView.backgroundColor = UIColor.red
}, completion: nil)
}
}
// or the issue is here in the handle function for the PanGesture.
#objc private func translateView(sender: UIPanGestureRecognizer) {
if let window = UIApplication.shared.keyWindow {
let translation = sender.translation(in: window)
switch sender.state {
case .began, .changed:
backView.center = CGPoint(x: backView.center.x, y: backView.center.y + translation.y)
sender.setTranslation(CGPoint.zero, in: window)
break
case .ended:
break
default:
break
}
}
}
}
class ObjectScrollView: UIScrollView{
}
class AvailableObject: UIView{
var information: String!
}
class MySCNViewController: UIViewController {
#IBOutlet weak var oScrollView: ObjectScrollView!
// this is a delegate method which gets called when an object is tapped in the scrollView
func scrollViewObjectIsTapped(_ objectScrollView: ObjectScrollView, object: AvailableObject) {
adjunctiveTextView.showView(passInObject: object)
}
let adjunctiveTextView = AdjunctiveTextView()
let ao = AvailableObject.init(frame: CGRect.init(x: 0, y: 0, width: 200, height: 200))
override func viewDidLoad() {
super.viewDidLoad()
ao.information = "test"
adjunctiveTextView.scrollView = oScrollView
ao.backgroundColor = UIColor.yellow
}
#IBAction func tap(_ sender: Any?){
scrollViewObjectIsTapped(oScrollView, object: ao)}
}
I've written a custom segue to get a fade effect, which i'm trying to achieve by inserting the destination view controller below the source view controller and animating the alpha of the source view controller to zero.
However, adding the destination view controller below the source seems to cancel the animation and the segue performs as if it is a regular present with animation disabled.
import UIKit
class FadeSegue: UIStoryboardSegue {
override func perform() {
// Get the view of the source
let sourceViewControllerView = self.sourceViewController.view
// Get the view of the destination
let destinationViewControllerView = self.destinationViewController.view
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height
// Make the destination view the size of the screen
destinationViewControllerView.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
if let window = UIApplication.sharedApplication().keyWindow {
// Insert destination below the source
// Without this line the animation works but the transition is not smooth as it jumps from white to the new view controller
window.insertSubview(destinationViewControllerView, belowSubview: sourceViewControllerView)
// Animate the fade, remove the destination view on completion and present the full view controller
UIView.animateWithDuration(0.4, animations: {
sourceViewControllerView.alpha = 0.0
}, completion: { (finished) in
destinationViewControllerView.removeFromSuperview()
self.sourceViewController.presentViewController(self.destinationViewController, animated: false, completion: nil)
})
}
}
}
Hello I think that what you need is this
override func perform() {
// Get the view of the source
let sourceViewControllerView = self.sourceViewController.view
// Get the view of the destination
let destinationViewControllerView = self.destinationViewController.view
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height
// Make the destination view the size of the screen
destinationViewControllerView.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
// Insert destination below the source
// Without this line the animation works but the transition is not smooth as it jumps from white to the new view controller
destinationViewControllerView.alpha = 0;
sourceViewControllerView.addSubview(destinationViewControllerView);
// Animate the fade, remove the destination view on completion and present the full view controller
UIView.animateWithDuration(10, animations: {
destinationViewControllerView.alpha = 1;
}, completion: { (finished) in
destinationViewControllerView.removeFromSuperview()
self.sourceViewController.presentViewController(self.destinationViewController, animated: false, completion: nil)
})
}
}
I Hope this help you
The slightly modified Swift 4.1 compatible solution based on the answer from #reinier-melian
/// Performs a simple fade between source and destination view controller.
class Fade: UIStoryboardSegue {
override func perform() {
guard let destinationView = self.destination.view else {
// Fallback to no fading
self.source.present(self.destination, animated: false, completion: nil)
return
}
destinationView.alpha = 0
self.source.view?.addSubview(destinationView)
UIView.animate(withDuration: CATransaction.animationDuration(), animations: {
destinationView.alpha = 1
}, completion: { _ in
self.source.present(self.destination, animated: false, completion: nil)
})
}
}
This is what I ended up doing for my purposes. It puts a placeholder view with basic ui elements over the top once all the on screen things have animated out and fades in the placeholder, then presents the destination view controller and removes the placeholder. The destination view controller then animates in all its elements, essentially making the transition invisible and looks like one single animation:
import UIKit
class FadeSegue: UIStoryboardSegue {
var placeholderView: UIViewController?
override func perform() {
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height
if let placeholder = placeholderView {
placeholder.view.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
placeholder.view.alpha = 0
sourceViewController.view.addSubview(placeholder.view)
UIView.animateWithDuration(0.4, animations: {
placeholder.view.alpha = 1
}, completion: { (finished) in
self.sourceViewController.presentViewController(self.destinationViewController, animated: false, completion: {
placeholder.view.removeFromSuperview()
})
})
} else {
self.destinationViewController.view.alpha = 0.0
self.sourceViewController.presentViewController(self.destinationViewController, animated: false, completion: {
UIView.animateWithDuration(0.4, animations: {
self.destinationViewController.view.alpha = 1.0
})
})
}
}
}
and the unwind:
import UIKit
class FadeSegueUnwind: UIStoryboardSegue {
var placeholderView: UIViewController?
override func perform() {
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height
if let placeholder = placeholderView {
placeholder.view.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
placeholder.view.alpha = 0
sourceViewController.view.addSubview(placeholder.view)
UIView.animateWithDuration(0.4, animations: {
placeholder.view.alpha = 1
}, completion: { (finished) in
self.sourceViewController.dismissViewControllerAnimated(false, completion: nil)
})
} else {
self.destinationViewController.view.alpha = 0.0
self.sourceViewController.dismissViewControllerAnimated(false, completion: {
UIView.animateWithDuration(0.4, animations: {
self.destinationViewController.view.alpha = 0.0
})
})
}
}
}
I want put several UIViewController together:
leftViewController.view.frame = CGRect(x: -200, y: 0.0, width: size.width/drawerSize, height: size.height)
// Center Drawer
centerViewController.view.frame = CGRect(x: leftViewController.view.frame.width, y: 0.0, width: centerWidth, height: size.height)
// Right Drawer
rightViewController.view.frame = CGRect(x: centerViewController.view.frame.origin.x + centerViewController.view.frame.size.width, y: 0.0, width: size.width/drawerSize, height: size.height)
In the first line I use
leftViewController.view.frame = CGRect(x: -200, y....)
They will show correctly but I can not click buttons on leftViewController.
If
leftViewController.view.frame = CGRect(x: 0.0, y...)
then could click button but this layout is not I want.
The full code is posted by #Kevin Scardina Slide Sidebar Menu IOS 8 Swift
It now function as the picture below, And I'm trying to modify it like a slide menu bar which could hid left and right menu.
/*
To use simply instantiate NVMDrawerController as your root view in your AppDelegate, or in the
StoryBoard.
Once NVMDrawerController is instantiated, set the drawerSize of the NVMDrawerController,
and its leftViewControllerIdentifier, centerViewControllerIdentifier, and
rightViewControllerIdentifier to the Storyboard Identifier of the UIViewController
you want in the different locations.
*/
class NVMDrawerController: UIViewController {
// This is where you set the drawer size (i.e. for 1/3rd use 3.0, for 1/5 use 5.0)
var drawerSize:CGFloat = 4.0
var leftViewControllerIdentifier:String = "LeftController"
var centerViewControllerIdentifier:String = "CenterController"
var rightViewControllerIdentifier:String = "RightController"
private var _leftViewController:UIViewController?
var leftViewController:UIViewController {
get{
if let vc = _leftViewController {
return vc;
}
return UIViewController();
}
}
private var _centerViewController:UIViewController?
var centerViewController:UIViewController {
get{
if let vc = _centerViewController {
return vc;
}
return UIViewController();
}
}
private var _rightViewController:UIViewController?
var rightViewController:UIViewController {
get{
if let vc = _rightViewController {
return vc;
}
return UIViewController();
}
}
static let NVMDrawerOpenLeft = 0
static let NVMDrawerOpenRight = 1
var openSide:Int {
get{
return _openSide;
}
}
private var _openSide:Int = NVMDrawerOpenLeft
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Instantiate VC's with storyboard ID's
_leftViewController = instantiateViewControllers(leftViewControllerIdentifier)
_centerViewController = instantiateViewControllers(centerViewControllerIdentifier)
_rightViewController = instantiateViewControllers(rightViewControllerIdentifier)
// Call configDrawers() and pass the drawerSize variable.
drawDrawers(UIScreen.mainScreen().bounds.size)
self.view.addSubview(leftViewController.view)
self.view.addSubview(centerViewController.view)
self.view.addSubview(rightViewController.view)
}
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
coordinator.animateAlongsideTransition({ (UIViewControllerTransitionCoordinatorContext) -> Void in
// This is for beginning of transition
self.drawDrawers(size)
}, completion: { (UIViewControllerTransitionCoordinatorContext) -> Void in
// This is for after transition has completed.
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Drawing View
func drawDrawers(size:CGSize) {
// Calculate Center View's Size
let centerWidth = (size.width/drawerSize) * (drawerSize - 1)
// Left Drawer
leftViewController.view.frame = CGRect(x: 0.0, y: 0.0, width: size.width/drawerSize, height: size.height)
// Center Drawer
centerViewController.view.frame = CGRect(x: leftViewController.view.frame.width, y: 0.0, width: centerWidth, height: size.height)
// Right Drawer
rightViewController.view.frame = CGRect(x: centerViewController.view.frame.origin.x + centerViewController.view.frame.size.width, y: 0.0, width: size.width/drawerSize, height: size.height)
//rightViewController = rc
// Capture the Swipes
let swipeRight = UISwipeGestureRecognizer(target: self, action: Selector("swipeRightAction:"))
swipeRight.direction = .Right
centerViewController.view.addGestureRecognizer(swipeRight)
let swipeLeft = UISwipeGestureRecognizer(target: self, action: Selector("swipeLeftAction:"))
swipeLeft.direction = .Left
centerViewController.view.addGestureRecognizer(swipeLeft)
if(openSide == NVMDrawerController.NVMDrawerOpenLeft){
openLeftDrawer()
}
else{
openRightDrawer()
}
}
// MARK: - Open Drawers
func openLeftDrawer() {
_openSide = NVMDrawerController.NVMDrawerOpenLeft
UIView.animateWithDuration(0.1, delay: 0, options: UIViewAnimationOptions.CurveEaseIn, animations:
{ () -> Void in
// move views here
self.view.frame = CGRect(x: 0.0, y: 0.0, width: self.view.bounds.width, height: self.view.bounds.height)
}, completion:
{ finished in
})
}
func openRightDrawer() {
_openSide = NVMDrawerController.NVMDrawerOpenRight
UIView.animateWithDuration(0.1, delay: 0, options: UIViewAnimationOptions.CurveEaseIn, animations:
{ () -> Void in
// move views here
self.view.frame = CGRect(x: self.view.bounds.origin.x - self.leftViewController.view.bounds.size.width, y: 0.0, width: self.view.bounds.width, height: self.view.bounds.height)
}, completion:
{ finished in
})
}
// MARK: - Swipe Handling
func swipeRightAction(rec: UISwipeGestureRecognizer){
self.openLeftDrawer()
}
func swipeLeftAction(rec:UISwipeGestureRecognizer){
self.openRightDrawer()
}
// MARK: - Helpers
func instantiateViewControllers(storyboardID: String) -> UIViewController {
if let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("\(storyboardID)") as? UIViewController{
return viewController;
}
return UIViewController();
}
}
When your view is outside of its superview,it can't receive any touch events.You should enumerate subviews in UIView(its superview) touchWithEvents function and make it receive the event.
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.