Andreas Du Rietz
Andreas Du Rietz

Reputation: 1171

hidesBarsOnSwipe never shows navbar again when scrolling up

So I want to hide the navbar when scrolling down and bring it back when scrolling up. Hiding it works perfectly with

self.navigationController?.hidesBarsOnSwipe = true

But I expect it to be shown again when scrolling up. I made a test project where the view controller just has a single UICollectionView that covers the whole screen. Then showing the navbar is shown again as expected until I add this line to the viewDidLoad (adding cells to the collection view):

self.collectionView.delegate = self

And this is what the whole view controller looks like

class ViewController: UIViewController,UICollectionViewDataSource, UICollectionViewDelegate {

@IBOutlet var collectionView: UICollectionView!
override func viewDidLoad() {
    super.viewDidLoad()
    self.collectionView.dataSource = self
    self.collectionView.delegate = self
    self.collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: "Test")
    self.navigationController?.hidesBarsOnSwipe = true
}

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return 3
}

func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
    return 1
}

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    return collectionView.dequeueReusableCellWithReuseIdentifier("Test", forIndexPath: indexPath) as UICollectionViewCell
}

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
    return CGSizeMake(300, 300)
}
}

So why does showing the navbar stop working when I add cells to my collection view?

Upvotes: 25

Views: 10033

Answers (7)

krlbsk
krlbsk

Reputation: 1061

To make hidesBarsOnSwipe working properly, your view controller's view must contain only UITableView instance and nothing else.

Upvotes: 0

Ramprasad A
Ramprasad A

Reputation: 231

I tried setting hidesBarsOnSwipe property to true in my ViewController class in ViewDidLoad function as given below, but it didn't work in handling hiding the navigation bar on swipe-up and unhiding the navigation bar on swipe-down.

class ViewController: UIViewController {

   override func viewDidLoad() {
      super.viewDidLoad()
      self.navigationController?.hidesBarsOnSwipe = true
   }
}

Setting hidesBarsOnSwipe to true will have effect only if we are using the UITableViewController or UICollectionViewController as main screens, hidesBarsOnSwipe will not work if we have added a UITableView to the UIViewController for displaying the list of data.

Solution

class TestTableViewController: UITableViewController {

    override func viewDidLoad() {
       super.viewDidLoad()
       self.navigationController?.hidesBarsOnSwipe = true
    }
}

Hope this answer might help...!

Upvotes: 3

Noufal Kmc
Noufal Kmc

Reputation: 2020

I had same issue. When I added the code for hiding status bar along with navigation bar, it worked.

- (BOOL)prefersStatusBarHidden {
  return self.navigationController.isNavigationBarHidden;
}

Upvotes: 3

johndpope
johndpope

Reputation: 5257

As per previous comments - this seems like a bug as of ios 10.3

as you are using a uicollectionview - I draw your attention to some code I re-wrote from APDynamicHeaderTableViewController https://github.com/aaronpang/APDynamicHeaderTableViewController/issues/4

It's using snapkit https://github.com/SnapKit/SnapKit

(Apologies to all the IB + NSLayout Constraint lovers.)

class APDynamicHeaderTableViewController : UIViewController {

  var largeWideSize = CGSize(width: UIScreen.main.bounds.width , height: 285 )
  let headerView = APDynamicHeaderView () // Change your header view here

  let cellLayout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
  var feedCV:UICollectionView!

  fileprivate var headerViewHeight:CGFloat = 80 // this will be updated by scrolling
  fileprivate var headerBeganCollapsed = false
  fileprivate var collapsedHeaderViewHeight : CGFloat = UIApplication.shared.statusBarFrame.height
  fileprivate var expandedHeaderViewHeight : CGFloat = 100
  fileprivate var headerExpandDelay : CGFloat = 100
  fileprivate var tableViewScrollOffsetBeginDraggingY : CGFloat = 0.0


  init(collapsedHeaderViewHeight : CGFloat, expandedHeaderViewHeight : CGFloat, headerExpandDelay :CGFloat) {
    self.collapsedHeaderViewHeight = collapsedHeaderViewHeight
    self.expandedHeaderViewHeight = expandedHeaderViewHeight
    self.headerExpandDelay = headerExpandDelay
    super.init(nibName: nil, bundle: nil)


  }


  init () {
    super.init(nibName: nil, bundle: nil)

  }

  required init(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  override func loadView() {
    super.loadView()
    self.view.backgroundColor = .green

    // Cell Layout Sizes
    cellLayout.scrollDirection = .vertical
    cellLayout.sectionInset = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
    cellLayout.itemSize = CGSize(width: UIScreen.main.bounds.width, height: 185 + 80)


    // Header view
    self.view.addSubview(headerView)
    headerView.snp.remakeConstraints { (make) -> Void in
        make.top.left.equalToSuperview()
        make.width.equalToSuperview()
        make.height.equalTo(headerViewHeight)
    }

    // CollectionView
    feedCV = UICollectionView(frame: .zero, collectionViewLayout: cellLayout)
    self.view.addSubview(feedCV)
    self.feedCV.snp.remakeConstraints { (make) -> Void in
        make.top.equalTo(headerView.snp.bottom) // this is pegged to the header view which is going to grow in height
        make.left.equalToSuperview()
        make.width.equalToSuperview()
        make.bottom.equalToSuperview()
    }



    feedCV.backgroundColor = .red
    feedCV.showsVerticalScrollIndicator = true
    feedCV.isScrollEnabled = true
    feedCV.bounces = true
    feedCV.delegate = self
    feedCV.dataSource = self

     // YOUR COLLECTIONVIEW CELL HERE!!!!!
    feedCV.register(VideoCollectionViewCell.self, forCellWithReuseIdentifier: VideoCollectionViewCell.ID)


  }

  // Animate the header view to collapsed or expanded if it is dragged only partially
  func animateHeaderViewHeight () -> Void {
    Logger.verbose("animateHeaderViewHeight")

    var headerViewHeightDestinationConstant : CGFloat = 0.0
    if (headerViewHeight < ((expandedHeaderViewHeight - collapsedHeaderViewHeight) / 2.0 + collapsedHeaderViewHeight)) {
      headerViewHeightDestinationConstant = collapsedHeaderViewHeight
    } else {
      headerViewHeightDestinationConstant = expandedHeaderViewHeight
    }

    if (headerViewHeight != expandedHeaderViewHeight && headerViewHeight != collapsedHeaderViewHeight) {
      let animationDuration = 0.25
      UIView.animate(withDuration: animationDuration, animations: { () -> Void in
        self.headerViewHeight = headerViewHeightDestinationConstant
        let progress = (self.headerViewHeight - self.collapsedHeaderViewHeight) / (self.expandedHeaderViewHeight - self.collapsedHeaderViewHeight)
        self.headerView.expandToProgress(progress)
        self.view.layoutIfNeeded()
      })
    }
  }
}

extension APDynamicHeaderTableViewController : UICollectionViewDelegate {

}

extension APDynamicHeaderTableViewController : UIScrollViewDelegate {
  func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
    // Clamp the beginning point to 0 and the max content offset to prevent unintentional resizing when dragging during rubber banding
    tableViewScrollOffsetBeginDraggingY = min(max(scrollView.contentOffset.y, 0), scrollView.contentSize.height - scrollView.frame.size.height)

    // Keep track of whether or not the header was collapsed to determine if we can add the delay of expansion
    headerBeganCollapsed = (headerViewHeight == collapsedHeaderViewHeight)
  }

  func scrollViewDidScroll(_ scrollView: UIScrollView) {
    // Do nothing if the table view is not scrollable
    if feedCV.contentSize.height < feedCV.bounds.height {
      return
    }
    var contentOffsetY = feedCV.contentOffset.y - tableViewScrollOffsetBeginDraggingY
    // Add a delay to expanding the header only if the user began scrolling below the allotted amount of space to actually expand the header with no delay (e.g. If it takes 30 pixels to scroll up the scrollview to expand the header then don't add the delay of the user started scrolling at 10 pixels)
    if tableViewScrollOffsetBeginDraggingY > ((expandedHeaderViewHeight - collapsedHeaderViewHeight) + headerExpandDelay) && contentOffsetY < 0 && headerBeganCollapsed {
      contentOffsetY = contentOffsetY + headerExpandDelay
    }
    // Calculate how much the header height will change so we can readjust the table view's content offset so it doesn't scroll while we change the height of the header
    let changeInHeaderViewHeight = headerViewHeight - min(max(headerViewHeight - contentOffsetY, collapsedHeaderViewHeight), expandedHeaderViewHeight)
    headerViewHeight = min(max(headerViewHeight - contentOffsetY, collapsedHeaderViewHeight), expandedHeaderViewHeight)
    let progress = (headerViewHeight - collapsedHeaderViewHeight) / (expandedHeaderViewHeight - collapsedHeaderViewHeight)
//    Logger.verbose("headerViewHeight:",headerViewHeight)
    headerView.expandToProgress(progress)
    headerView.snp.updateConstraints { (make) -> Void in
        make.height.equalTo(headerViewHeight)
    }

    // When the header view height is changing, freeze the content in the table view
    if headerViewHeight != collapsedHeaderViewHeight && headerViewHeight != expandedHeaderViewHeight {
        feedCV.contentOffset = CGPoint(x: 0, y: feedCV.contentOffset.y - changeInHeaderViewHeight)
    }
  }

  // Animate the header view when the user ends dragging or flicks the scroll view 
  func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    animateHeaderViewHeight()
  }

  func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    animateHeaderViewHeight()
  }
}

extension APDynamicHeaderTableViewController : UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 100
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: VideoCollectionViewCell.ID, for: indexPath) as! VideoCollectionViewCell

        return cell
    }


    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {

    }



     func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return largeWideSize
    }




}

Upvotes: 0

iOS dev
iOS dev

Reputation: 580

To expand on Oleg's answer...

If you are using Interface Builder to set a constraint to a view controller's primary view, Xcode defaults to showing options to set the vertical constraint against the top layout guide. However, if you press 'Option', you will see an alternate set of constraints. The constraint for 'Top Space to Container' is what you're looking for.

Upvotes: 10

Oleg Sherman
Oleg Sherman

Reputation: 2802

I had the same problem but with a web view. The problem was that the top constraint of the web view was "Top Layout Guide.Top" , after changing the top constraint to "Superview.Top" the problem was solved.

Upvotes: 47

Andreas Du Rietz
Andreas Du Rietz

Reputation: 1171

I filed a bug report with Apple and ended up using AMScrollingNavbar instead which works really well and is easy to setup.

Upvotes: 1

Related Questions