I have a custom tab Bar where i add a button in the middle:
class CustomTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
setupMiddleButton()
}
func setupMiddleButton() {
let numberOfItems = CGFloat(tabBar.items!.count)
let tabBarItemSize = CGSize(width: tabBar.frame.width / numberOfItems, height: tabBar.frame.height)
menuButton.frame = CGRect(x: 0, y: 0, width: tabBarItemSize.width, height: tabBar.frame.size.height)
var menuButtonFrame = menuButton.frame
menuButtonFrame.origin.y = self.view.bounds.height - menuButtonFrame.height - self.view.safeAreaInsets.bottom
menuButtonFrame.origin.x = self.view.bounds.width/2 - menuButtonFrame.size.width/2
menuButton.frame = menuButtonFrame
menuButton.backgroundColor = UIColor.clear
menuButton.addTarget(self, action: #selector(menuButtonAction), for: UIControlEvents.touchUpInside)
self.view.addSubview(menuButton)
self.view.layoutIfNeeded()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
menuButton.frame.origin.y = self.view.bounds.height - menuButton.frame.height - self.view.safeAreaInsets.bottom
}
}
This bar is shown in multiple controller.
However i have a specific controller where i'd like the tab bar to be hidden.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.tabBar.isHidden = true
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.tabBarController?.tabBar.isHidden = false
}
This code works fine and the bar is actually hidden.
However If i click in the middle (where the menuButton is added) the button action is called (a segue is performed).
How can I disable the button when hiding the Tab bar?
Thank you for the help!
--------------UPDATE Solution
I am not sure this is the best solution because i'm new to swift, but it seems to work...
in my CustomTabBarController I have added to function:
func hideTabBar() {
self.tabBar.isHidden = true
self.menuButton.isHidden = true
}
func showTabBar() {
self.tabBar.isHidden = false
self.menuButton.isHidden = false
}
the whenever i need to hide/display it i call this functions.
In my case in the controller where i'd like to hide it i do so:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let tabBar = self.tabBarController as! FishBookTabBarController
tabBar.hideTabBar()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
let tabBar = self.tabBarController as! FishBookTabBarController
tabBar.showTabBar()
}
You are adding your button to self.view, so it is not "part of" your tab bar.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.tabBar.isHidden = true
self.menuButton.isHidden = true
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.tabBarController?.tabBar.isHidden = false
self.menuButton.isHidden = false
}
That should do it.
I created demo project to show the problem.
We have two view controllers inside UINavigationController.
MainViewController which is the root.
class MainViewController: UIViewController {
lazy var button: UIButton = {
let button = UIButton()
button.setTitle("Detail", for: .normal)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Main"
view.backgroundColor = .blue
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
button.widthAnchor.constraint(equalToConstant: 150).isActive = true
button.heightAnchor.constraint(equalToConstant: 42).isActive = true
button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
}
#objc func buttonTapped(_ sender: UIButton) {
navigationController?.pushViewController(DetailViewController(), animated: true)
}
}
And DetailViewController which is pushed.
class DetailViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.setNavigationBarHidden(false, animated: animated)
}
}
As you can see I want to hide UINavigationBar in DetailViewController:
Question
The problem is that, UINavigationBar slides away instead of stay of his place together with whole MainViewController. How can I change that behavior and keep pop gesture?
in your MainViewController add that method
override func viewDidAppear(_ animated: Bool) {
UIView.animate(withDuration: 0) {
self.navigationController?.setNavigationBarHidden(false, animated: false)
}
}
and replace your method with below method in DetailViewController
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.setNavigationBarHidden(true, animated: animated)
}
The following code is hacking.
override func viewDidAppear(_ animated: Bool) {
UIView.animate(withDuration: 0) {
self.navigationController?.setNavigationBarHidden(false, animated: false)
}
}
Do not write this bizarre code, as suggested by #sagarbhut in his post (in this thread).
You have two choices.
Hack
Do not hack.
Use convenience functions like this one
https://developer.apple.com/documentation/uikit/uiview/1622562-transition
Create a custom segue, if you are using storyboards.
https://www.appcoda.com/custom-segue-animations/
Implement the UIViewControllerAnimatedTransitioning protocol
https://developer.apple.com/documentation/uikit/uiviewcontrolleranimatedtransitioning
You can get some great results but I'm afraid you will need to work hard. There are numerous tutorials online that discuss how to implement the above.
Twitter's navigation transition where the pushed ViewController's view seems to take the entire screen "hiding the navigationBar", but still having the pop gesture animation and the navigationBar visible in the pushing ViewController even during the transition animation obviously cannot be achieved by setting the bar's hidden property.
Implementing a custom navigation system is one way to do it but I suggest a simple solution by playing on navigationBar's layer and its zPosition property. You need two steps,
Set the navigationBar's layer zPosition to a value that'd place it under its siblings which include the current visible view controller's view in the navigation stack: navigationController?.navigationBar.layer.zPosition = -1
The pushing VC's viewDidLoad could be a good place to do that.
Now that the navigationBar is placed behind the VC's view, you'll need to adjust the view's frame to make sure it doesn't overlap with the navigationBar (that'd cause navigationBar to be covered). You can use viewWillLayoutSubviews to change the view's origin.y to start under navigationBar's floor (statusBarHeight + navigationBarHeight).
That'll do the job. You don't need to modify the pushed VC unless you wanna add e.g. a custom back button like in the Twitter's profile screen case. The detail controller's view will be on top of navigation bar while letting you keep the pop gesture transition. Below is your sample code modified with this changes:
class MainViewController: UIViewController {
lazy var button: UIButton = {
let button = UIButton()
button.setTitle("Detail", for: .normal)
button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Main"
view.backgroundColor = .blue
// Default value of layer's zPosition is 0 so setting it to -1 will place it behind its siblings.
navigationController?.navigationBar.layer.zPosition = -1
// The `view` will be under navigationBar so lets set a background color to the bar
// as the view's backgroundColor to simulate the default behaviour.
navigationController?.navigationBar.backgroundColor = view.backgroundColor
// Hide the back button transition image.
navigationController?.navigationBar.backIndicatorImage = UIImage()
navigationController?.navigationBar.backIndicatorTransitionMaskImage = UIImage()
view.addSubview(button)
addConstraints()
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
// Place `view` under navigationBar.
let statusBarPlusNavigationBarHeight: CGFloat = (navigationController?.navigationBar.bounds.height ?? 0)
+ UIApplication.shared.statusBarFrame.height
let viewHeight = UIScreen.main.bounds.height - statusBarPlusNavigationBarHeight
view.frame = CGRect(origin: .zero, size: CGSize(width: view.bounds.width, height: viewHeight))
view.frame.origin.y = statusBarPlusNavigationBarHeight
}
#objc func buttonTapped(_ sender: UIButton) {
navigationController?.pushViewController(DetailViewController(), animated: true)
}
private func addConstraints() {
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
button.widthAnchor.constraint(equalToConstant: 150).isActive = true
button.heightAnchor.constraint(equalToConstant: 42).isActive = true
}
}
class DetailViewController: UIViewController {
// Some giant button to replace the navigationBar's back button item :)
lazy var button: UIButton = {
let b: UIButton = UIButton(frame: CGRect(origin: .zero, size: CGSize(width: 80, height: 40)))
b.frame.origin.y = UIApplication.shared.statusBarFrame.height
b.backgroundColor = .darkGray
b.setTitle("back", for: .normal)
b.addTarget(self, action: #selector(DetailViewController.backButtonTapped), for: .touchUpInside)
return b
}()
#objc func backButtonTapped() {
navigationController?.popViewController(animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(button)
}
}
This might be what you're looking for...
Start the NavBar hide / show animations before starting the push / pop:
class MainViewController: UIViewController {
lazy var button: UIButton = {
let button = UIButton()
button.setTitle("Detail", for: .normal)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Main"
view.backgroundColor = .blue
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
button.widthAnchor.constraint(equalToConstant: 150).isActive = true
button.heightAnchor.constraint(equalToConstant: 42).isActive = true
button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
}
#objc func buttonTapped(_ sender: UIButton) {
navigationController?.setNavigationBarHidden(true, animated: true)
navigationController?.pushViewController(DetailViewController(), animated: true)
}
}
class DetailViewController: UIViewController {
lazy var button: UIButton = {
let button = UIButton()
button.setTitle("Go Back", for: .normal)
button.backgroundColor = .red
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
button.widthAnchor.constraint(equalToConstant: 150).isActive = true
button.heightAnchor.constraint(equalToConstant: 42).isActive = true
button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
}
#objc func buttonTapped(_ sender: UIButton) {
navigationController?.setNavigationBarHidden(false, animated: true)
navigationController?.popViewController(animated: true)
}
}
Use the custom push transition from this post stackoverflow.com/a/5660278/7270113. The in order to eliminate the back gesture (that's what I understand is what you want to do), just kill the navigation stack. You will have to provide an alternative way to exit the DetailViewController, as even if you unhide the navigation controller, the backbitten will be gone since the navigation stack is empty.
#objc func buttonTapped(_ sender: UIButton) {
let transition = CATransition()
transition.duration = 0.5
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionFade
navigationController?.view.layer.add(transition, forKey: nil)
let storyboard = UIStoryboard(name: "NameOfYourStoryBoard", bundle: .main)
let viewController = storyboard.instantiateViewController(withIdentifier: "IdentifierOfDetailViewController") as! DetailViewController
navigationController?.setViewControllers([viewController], animated: true) // This method will perform a push
}
Your navigation controller will from now on use this transition animation, if you want to remove it you could use
navigationController?.view.layer.removeAllAnimations()
I have a firstViewController which has a UISegmentedControl and SegmentedControl has two tabs button. its all working fine but when i go to next SecondViewController. On SecondViewController there is a link for webView when i push to that webView & then back to SecondViewController and then back to firstViewController by tapping back button. The SegmentedControl leave its position & it moves below the NavigationController
firstViewController Code is :
override func viewDidLoad() {
super.viewDidLoad()
setupMenuBar()
setupViewControllerUI()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setupNavigationBarUI()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewDidAppear(animated)
// to avoid getting a black navigationBar while transition
navigationController?.navigationBar.barTintColor = UIColor.white
self.navigationController?.navigationBar.setBackgroundImage(UIColor.white.convertImage(), for: UIBarMetrics.default)
navigationController!.navigationBar.titleTextAttributes = [ NSFontAttributeName: UIFont.appThemeRegularFontWithSize(20.0), NSForegroundColorAttributeName: UIColor.lightGray]
}
// MARK: - UIViewController Helper Method
func setupViewControllerUI() {
if isFirstVC {
AppUtility.switchToViewController(viewController: firstViewController!, in: self)
segmentedControl.selectedSegmentIndex = 0
} else {
LoadingViewController.sharedLoader.showLoading(self.navigationController!)
AppUtility.switchToViewController(viewController: secondViewController, in: self)
segmentedControl.selectedSegmentIndex = 1
}
}
func setupMenuBar() {
SegmentedControlContainerView.backgroundColor = UIColor.ButtonColorWithAlpha(1.0)
segmentedControl.tintColor = UIColor.white
}
func setupNavigationBarUI() {
navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName : UIColor.white]
navigationController?.navigationBar.barTintColor = UIColor.ButtonColorWithAlpha(1.0)
navigationController?.navigationBar.setBackgroundImage(UIColor.ButtonColorWithAlpha(1.0).convertImage(), for: UIBarMetrics.default)
navigationController?.navigationBar.shadowImage = UIColor.white.withAlphaComponent(0.0).convertImage()
navigationController?.view.backgroundColor = UIColor.ButtonColorWithAlpha(1.0)
segmentedControl.setTitle("first", forSegmentAt: 1)
segmentedControl.setTitle("second", forSegmentAt: 0)
self.navigationController?.navigationBar.tintColor = UIColor.white
navigationController?.navigationBar.barStyle = UIBarStyle.default
self.title = "Screen Name"
navigationController?.navigationBar.setNeedsDisplay()
}
I am working on pretty simple animation in navigation bar: I have navigationbar bar with 2 buttons on the (right). On search icon tap I animate nav bar so it hides both button and search icon and shows search bar. On cancel button tap it does the opposite. Simple
lazy var searchBar:UISearchBar = UISearchBar()
override func viewDidLoad() {
super.viewDidLoad()
self.automaticallyAdjustsScrollViewInsets = false
searchBar.delegate = self
searchBar.backgroundColor = UIColor.darkGray
searchBar.tintColor = UIColor(red: 140/255, green: 195/255, blue: 65/255, alpha: 1.0)
}
On tap of search button search bar is added to navigation bar,but its slightly cut's off on the left as well as right side.
#IBAction func subjectTapped(_ sender: Any) {
self.navigationItem.setRightBarButtonItems(nil, animated: true)
self.navigationItem.setLeftBarButtonItems(nil,animated: true)
self.navigationItem.titleView = nil
searchBar.placeholder = "I am looking for ..."
searchBar.alpha = 0
self.navigationItem.titleView = searchBar
UIView.animate(withDuration: 0.5, animations: {
self.searchBar.alpha = 1
}, completion: { finished in
self.searchBar.becomeFirstResponder()
})
}
Please tell me what am i doing wrong and whats the fix for this problem
I'm trying to achieve something similar to the Calendar App, so far i have below code which when i click on the leftBarButton nothing than a small empty white space appears? why is there no searchBar in it?
What i want is when u click a new navigationBar appear with a searchBar
illustration when i click searchButton:
code:
override func viewDidLoad() {
super.viewDidLoad()
//searchBar
theSearchBar = UISearchBar(frame: CGRectZero)
theSearchBar?.delegate = self
theSearchBar?.showsCancelButton = true
theSearchBar?.placeholder = "Søg efter produkter"
searchController = UISearchDisplayController(searchBar: theSearchBar!, contentsController: self)
searchController?.delegate = self
searchController?.searchResultsDelegate = self
searchController?.searchResultsDataSource = self
}
#IBAction func showSearchBar(sender: UIBarButtonItem) {
theSearchBar?.becomeFirstResponder()
searchController?.setActive(true, animated: true)
}