I would like to set the background color of a view to black when the navigation bar is hidden, and to white when the navigation bar is displayed.
The property hidesBarsOnTap is set to true in viewDidLoad. This works fine:
navigationController?.hidesBarsOnTap = true
How can I be notified when the bars are hidden and displayed?
Sorry, I made a mistake. The following code does exactly what you want. If you have a toolbar, you can set it to hide as well.
class ViewController: UIViewController {
var hidden = false {
didSet {
if let nav = navigationController {
nav.setNavigationBarHidden(hidden, animated: true)
nav.setToolbarHidden(hidden, animated: true)
view.backgroundColor = hidden ? UIColor.blackColor() : UIColor.whiteColor()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
let recognizer = UITapGestureRecognizer(target: self, action: "tap:")
view.addGestureRecognizer(recognizer)
}
func tap(recognizer: UITapGestureRecognizer) {
if recognizer.state == .Ended {
hidden = !hidden
}
}
}
since hidesBarsOnTap is of type boolean, we can easily use it to check and use it as option like in below example:
var set : Bool = navigationController?.hidesBarsOnTap //true or false
if (set){
//do what you want when set
}else{
//do what you want when it is not set
}
Related
I've got some subviews in my navigationBar. A black square, some titles and a blue bar. When pushed to a detail viewController, I want my subviews to hide (animated).
I added the subways to the navigation bar inside the main view controller using
self.navigationController?.navigationBar.addsubView(mySubview).
Currently, it looks like his:
What is the right way to hide (animated) those subviews in the detail viewController?
Your question is very interesting. I never thought about the navigation bar.
When UINavigationController is used as the root controller, all UIViewControllers are stored in its stack, meaning that all UIViewControllers share a navigationBar.
You added mySubView to navigationBar in the first UIViewController. If you want to hide it on the details page, you can search for subviews directly.
The first step, you need to give mySubView a tag, which can be a tag, or it can be a custom type, which is convenient for later judgment.
On the first page
import SnapKit
mySubView = UIView()
mySubView?.tag = 999
mySubView?.backgroundColor = .red
mySubView?.translatesAutoresizingMaskIntoConstraints = false
let navBar = navigationController?.navigationBar
navBar!.addSubview(mySubView!)
mySubView!.snp.makeConstraints { (make) in
make.left.equalTo(navBar!).offset(100)
make.centerY.equalTo(navBar!)
make.width.height.equalTo(50)
}
On the details page, I deleted isHidden and saved navigationBar with the attribute because navigationBar = nil during the gesture. If SnapKit is unfamiliar, take a few more minutes to learn.
var mySubView: UIView? = nil
var navBar: UINavigationBar?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
// Do any additional setup after loading the view.
navigationController?.interactivePopGestureRecognizer?.addTarget(self, action: #selector(backGesture))
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
homeNavigationBarStatus()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
homeNavigationBarStatus()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if mySubView == nil {
for view: UIView in (navigationController?.navigationBar.subviews)! {
if view.tag == 999 {
mySubView = view
}
}
}
UIView.animate(withDuration: 1, delay: 0, options: .curveEaseOut, animations: {
self.detailNavigationBarStatus()
}, completion: nil)
}
func detailNavigationBarStatus(){
changingNavigationBarStatus(progress: 0)
}
func homeNavigationBarStatus(){
changingNavigationBarStatus(progress: 1.0)
}
func changingNavigationBarStatus(progress:CGFloat){
mySubView?.alpha = progress
mySubView?.snp.updateConstraints({ (make) in
make.left.equalTo(navBar!).offset(100 * progress)
})
}
#objc func backGesture(sender: UIScreenEdgePanGestureRecognizer) {
switch sender.state {
case .changed:
let x = sender.translation(in: view).x
progress = x / view.frame.width
changingNavigationBarStatus(progress: progress)
default:
break
}
}
However, using tag values is not elegant enough, you can create a specific class for mySubView, which can also be judged by class.
I have a tableView. I set the all settings about searchController ( Search Bar in Large Navigation Bar ) - ( open / close when scroll tableview ). I implemented rightBarButtonItem which name is 'Close' . I want to hide/close tableView and Search Bar with programmatically. I can hide tableView but not SearchBar.
When I do isHidden for SearchBar , The Large Navigation Bar doesnt shrink to normal size.
Pic 1. Opened search bar with scroll down.
Pic 2. Not Hidden Large Navigation Bar with programmatically ( searchar.isHidden not implemented here )
Thanks in advance.
I tried this before but not run
tableView.setContentOffset(.zero, animated: false)
navigationController?.navigationBar.prefersLargeTitles = false
I tried to find a proper way to hide search bar, but I didn't find. But I found a workaround to hide your search bar which is change content offset your table view.
You may try this function to hide your table view and search bar.
func hide() {
tableView.isHidden = true
let point = tableView.contentOffset
let searchBarFrame = self.navigationItem.searchController?.searchBar.frame
let newPoint = CGPoint(x: point.x, y: point.y + searchBarFrame!.height)
tableView.setContentOffset(newPoint, animated: true)
}
Just try this:
navigationItem.searchController = nil
This is all my test code:
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var leftBarButtonItem: UIBarButtonItem!
var isHidden = false
var searchController: UISearchController {
let search = UISearchController(searchResultsController: nil)
search.searchBar.placeholder = "hello world"
search.obscuresBackgroundDuringPresentation = false
return search
}
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = "Test"
tableView.delegate = self
tableView.dataSource = self
showSearchController()
}
#IBAction func isHiddenAction(_ sender: UIBarButtonItem) {
isHidden = !isHidden
self.tableView.isHidden = isHidden
if isHidden {
leftBarButtonItem.title = "Show"
hiddenSearchController()
} else {
leftBarButtonItem.title = "Hidden"
showSearchController()
}
}
func hiddenSearchController() {
navigationItem.searchController = nil
}
func showSearchController() {
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = true
definesPresentationContext = true
}
How would I hide the navigation bar once my imageView is tapped, the navigation bar messes up the view of the full screen image once my imageView is tapped and I would like it hidden when the image is tapped and to reappear once the image is dismissed. Here is my code for my image being tapped.
//expandImage
#IBAction func expand(_ sender: UITapGestureRecognizer) {
let imageView = sender.view as! UIImageView
let newImageView = UIImageView(image: imageView.image)
newImageView.frame = self.view.frame
newImageView.backgroundColor = .black
newImageView.contentMode = .scaleAspectFit
newImageView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissFullscreen))
newImageView.addGestureRecognizer(tap)
self.view.addSubview(newImageView)
}
func dismissFullscreen(_ sender: UITapGestureRecognizer) {
sender.view?.removeFromSuperview()
}
Add this to your expand() method:
self.navigationController?.setNavigationBarHidden(true, animated: true)
And in dismissFullscreen() method:
self.navigationController?.setNavigationBarHidden(false, animated: true)
Or you can create new ViewController, pass image to it (with segue e.g) and add this to viewDidLoad() of new ViewController:
self.navigationController?.hidesBarsOnTap = true
So here is how you can do that:
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let tapImageScrollView = UITapGestureRecognizer(target: self, action: #selector(imageTapped(_:)))
imageView.isUserInteractionEnabled = true
imageView.addGestureRecognizer(tapImageScrollView)
}
func imageTapped(_ sender: UIGestureRecognizer) {
if self.navigationController?.navigationBar.isHidden == false {
self.navigationController?.navigationBar.isHidden = true
} else {
self.navigationController?.navigationBar.isHidden = false
}
}
}
So basically add a UITapGestureRecognizer to your imageView and in the imageTapped function you check if the navigationBar is not hidden then you want to show the image and hide the navigationBar and if you click on the imageView again you want to show the navigationBar again.
So simply add the logic in imageTapped to your dismissFullscreen function.
I tried calling tabBarController!.tabBar.hidden = true in viewDidLoad() and it hides the TabBar. However, I tried to set tap gesture and hide the bar on Tap. The parent viewController that has ScrollView inside it with subview (that is connected with IBOutlet as myView)
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: Selector("handleTap:"))
myView.addGestureRecognizer(tap)
}
func handleTap(sender: UITapGestureRecognizer? = nil) {
print("A") // logs successfully
if TabBarHidden == false {
print("B") // logs successfully
//I tried:
tabBarController?.tabBar.hidden = true
// I also tried
tabBarController?.tabBar.alpha = 0
tabBarController?.tabBar.frame.origin.x += 50
hidesBottomBarWhenPushed = true
} else {
...
TabBarHidden = false
}
}
hidden does work when I call it in viewDidLoad as I said, but not if I call in tap gesture function. What may be the problem? What am I missing?
this code totally works for me:
class ViewController: UIViewController {
var tabBarHidden: Bool = false {
didSet {
tabBarController?.tabBar.hidden = tabBarHidden
}
}
override func viewDidLoad() {
super.viewDidLoad()
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapGestureRecognized(_:)))
view.addGestureRecognizer(tapGestureRecognizer)
}
func tapGestureRecognized(sender: UITapGestureRecognizer) {
tabBarHidden = !tabBarHidden
}
}
iOS 8 adds a super new cool feature: hiding the navigation bar when user is scrolling.
This with a single line in viewDidload :
navigationController?.hidesBarsOnSwipe = true
Cool, isn't it?
But now I have a little problem: when the navigation bar is hidden, the status bar is still here and overlaps content, which is ugly.
What should I do to make it hidden when the navigation bar is hidden?
Override the following methods on UIViewController:
extension MyViewController {
override func prefersStatusBarHidden() -> Bool {
return barsHidden // this is a custom property
}
// Override only if you want a different animation than the default
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
return .slide
}
}
Update barsHidden somewhere in the code and call
setNeedsStatusBarAppearanceUpdate()
This is fixed problem for in Xcode 6.1
navigationController?.navigationBar.hidden = true
I am basing this answer on some comments on this post, which are speculation. I am not sure if this will work, because Apple does not give us any direct way or delegate method for when the navigation bar hides.
Subclass UINavigationBar as NavigationBar. Add a property observer to its hidden property like so:
var hidden: Bool{
didSet{
UIApplication.sharedApplication().setStatusBarHidden(self.hidden, animation: .Slide)
}
}
You want to then go to your viewDidLoad method in your main view controller, and set your self.navigationBar property (or self.navigationController.navigationBar, not sure which one) to an instance of your new NavigationBar class.
Note that I cannot test this right now, let me know how/if this works.
You could detect swipes by using UISwipeGestureRecognizer. I'm using it on UIWebView:
In viewDidLoad I have:
let swipeUp = UISwipeGestureRecognizer(target: self, action: "didSwipe")
let swipeDown = UISwipeGestureRecognizer(target: self, action: "didSwipe")
swipeUp.direction = UISwipeGestureRecognizerDirection.Up
swipeDown.direction = UISwipeGestureRecognizerDirection.Down
webView.addGestureRecognizer(swipeUp)
webView.addGestureRecognizer(swipeDown)
navigationController?.hidesBarsOnSwipe = true
I also have an extension to my viewcontroller, called WebViewViewController:
extension WebViewViewController {
override func prefersStatusBarHidden() -> Bool {
return hideStatusBar
}
override func preferredStatusBarUpdateAnimation() -> UIStatusBarAnimation {
return UIStatusBarAnimation.Slide
}
}
On a class level in my WebViewViewController I also have:
var hideStatusBar = false
func didSwipe() {
hideStatusBar = true
}
Okay I spent all day doing this, hopefully this helps some people out. There's a barHideOnSwipeGestureRecognizer. So you could make a listener for the corresponding UIPanGesture, noting that if the navigation bar is hidden then its y origin is -44.0; otherwise, it's 0 (not 20 because we hid the status bar!).
In your view controller:
// Declare at beginning
var curFramePosition: Double!
var showStatusBar: Bool = true
self.navigationController?.barHideOnSwipeGestureRecognizer.addTarget(self, action: "didSwipe:")
...
override func viewDidLoad(){
self.navigationController?.hidesBarsOnSwipe = true
curFramePosition = 0.0 // Not hidden
self.navigationController?.barHideOnSwipeGestureRecognizer.addTarget(self, action: "didSwipe:")
...
}
func didSwipe(swipe: UIPanGestureRecognizer){
// Visible to hidden
if curFramePosition == 0 && self.navigationController?.navigationBar.frame.origin.y == -44 {
curFramePosition = -44
showStatusBar = false
prefersStatusBarHidden()
setNeedsStatusBarAppearanceUpdate()
}
// Hidden to visible
else if curFramePosition == -44 && self.navigationController?.navigationBar.frame.origin.y == 0 {
curFramePosition = 0
showStatusBar = true
prefersStatusBarHidden()
setNeedsStatusBarAppearanceUpdate()
}
}
override func prefersStatusBarHidden() -> Bool {
if showStatusBar{
return false
}
return true
}