25

I have got a View Controller embedded in a Navigation Controller with prefers large titles option set to true; inside the View Controller there’s a Scroll View.

I want to make the nav bar shrink when scrolling.

How could I achieve this?

Xcode 9, Swift 4, iOS 11

pkamb
  • 33,281
  • 23
  • 160
  • 191
Jonathan Solorzano
  • 6,812
  • 20
  • 70
  • 131

12 Answers12

86

I have not achieved this using a UIScrollView but I achieved it with other ViewControllers using a UITableView as first view.

If the tableView is not the first view, the large title fails to hide automatically. You most likely need to make sure your tableView is the first element in the main view’s subviews array.

enter image description here

I hope that this solves your problem.

Tristan Newman
  • 357
  • 1
  • 4
  • 13
93sauu
  • 3,770
  • 3
  • 27
  • 43
  • I do not consider it a problem. In my App I wanted to keep the navigation bar always big (Not hide on scroll). – Markus May 30 '18 at 08:19
  • 1
    OP specifically asked for a solution **not** using UITableViewController – Andrew Kirna May 17 '19 at 23:03
  • 2
    If you are building a view programmatically, you have to add the table/ scrollview as a subview first. If you use "bringSubviewToFront" you'll lose the large title transition as well. – Tristan Newman Apr 02 '20 at 14:06
  • @TristanNewman what if I need that view on the back? I mean I need it as constraint in safe area but in the back of table view? because I want to create a view that is in the back of table view but also in the back of large title too. – Dylan Sep 07 '20 at 05:03
  • @TristanNewman Is there any way to use a scroll view inside another view? eg: viewController -> my_custome_view -> scrollView – Maneesh M Nov 02 '20 at 14:36
  • Well this isnt working to hide Large title when my tableview is first View. I addition I have given top constraint for safearea from different View which is not first View . – Kudos Jul 02 '21 at 07:22
  • Well this works only when you enable `bounce on scroll` for tableview – Kudos Jul 02 '21 at 08:00
8

Have you tried something like this? It converts the large title display mode when the content is scrolled up.

class P1ViewController: UIViewController, UIScrollViewDelegate
{
    var canTransitionToLarge = false
    var canTransitionToSmall = true    

    func scrollViewDidScroll(_ scrollView: UIScrollView)
    {
        if canTransitionToLarge && scrollView.contentOffset.y <= 0 {
            UIView.animate(withDuration: 0.5) {
                self.navigationItem.largeTitleDisplayMode = .always
            }
            canTransitionToLarge = false
            canTransitionToSmall = true
        }
        else if canTransitionToSmall && scrollView.contentOffset.y > 0 {
            UIView.animate(withDuration: 0.5) {
                self.navigationItem.largeTitleDisplayMode = .never
            }
            canTransitionToLarge = true
            canTransitionToSmall = false
        }
    }
}
Kamil Szostakowski
  • 2,153
  • 13
  • 19
  • This works, but it's not that smooth, doesn't feel as native as with tableviewcontrollers – Jonathan Solorzano Oct 14 '17 at 17:41
  • 1
    This helped; but I went for the other suggestion of nesting my modal `UITableViewController` in a `UINavigationController`. This allowed me to both: (1) display a `UINavigationBar` and (2) display large titles (automatically adjusting to scroll gestures) with `navigationController?.navigationBar.prefersLargeTitles = true`. – Andrew Kirna May 18 '19 at 19:02
  • If you prefer a standard `UIViewController` with a nested `UINavigationBar` and `UITextView` for scrolling content, you should consider using additional `UIScrollViewDelegate` methods like `scrollViewWillBeginDragging(_:)`, `scrollViewDidScroll(_:)`, and `scrollViewDidScrollToTop(_:)` to update `largeTitleDisplayMode` more often. Keep in mind, `UITextViewDelegate` inherits from `UIScrollViewDelegate` so you have access to all of those superclass methods. Remember to set the delegate in your view controller with an outlet from the `UITextView` using `textView.delegate = self` in `viewDidLoad()` – Andrew Kirna May 18 '19 at 19:25
6

Setting prefersLargeTitles in code did work for me to make the NavBar title shrink and grow as you scroll. I did notice if you set the property through InterfaceBuilder, the shrinking feature didn't work.

IB Property Inspector

Instead set in code like

    self.navigationController?.navigationBar.prefersLargeTitles = true
Maddiee
  • 288
  • 2
  • 11
billm
  • 124
  • 1
5

This way is working for me. I followed these steps:

  1. Moving the TableView to top of the list (under safe area) in view controller scene.
  2. Making FirstView and Loaded by FirstViewContoller.

The screenshot of the solution

pkamb
  • 33,281
  • 23
  • 160
  • 191
2

I used dsk1306 solutions, see above. But in my case I had a WebView in the UIViewController with the large title.

So I had to set the UIWebView ScrollView delegate:

self.webView.scrollView.delegate = self

and

extension MyViewController: UIScrollViewDelegate {

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    if #available(iOS 11.0, *) {
        UIView.animate(withDuration: 0.5, animations: {
            self.navigationController?.navigationBar.prefersLargeTitles = (velocity.y < 0)
        })
    }
}

}
pooopy
  • 311
  • 2
  • 9
2

I had the similar problem, is was because of many UIScrollView-based view inside view controller and I was scrolling not first in hierarchy. Once it's only one it works fine with UIScrollView, WKWebView or UITextView without any line of code.

avdyushin
  • 1,906
  • 17
  • 21
  • This worked for me too. I was adding a background view before the UIScrollView. Thanks! – Kqtr Nov 10 '21 at 15:51
1

I've used something that is similar to Kamil Szostakowski's answer for view with UICollectionView.

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    UIView.animate(withDuration: 0.5, animations: {
        self.navigationController?.navigationBar.prefersLargeTitles = (velocity.y < 0)
    })
}

It may be not so smooth but I haven't found better option yet.

dsk1306
  • 129
  • 1
  • 9
1

In case someone needs to have some view on top, like a menu, and then a UITableView/UICollectionView, and the large title is not hiding when scrolling, like in this "awesome" picture I made:

You will need to set up all the required constraints for the UIView & for your UITableView/UICollectionView, but the most vital part is:

make sure your UITableView/UICollectionView is the first subview, and then the UIView or any other views are behind.

Here is an example of that:

Starsky
  • 1,829
  • 18
  • 25
0

Try something like this:

    extendedLayoutIncludesOpaqueBars = true
    scrollView.translatesAutoresizingMaskIntoConstraints = false       
    view.addSubview(scrollView)
    NSLayoutConstraint.activate([
        scrollView.topAnchor.constraint(equalTo: view.topAnchor),
        scrollView.leftAnchor.constraint(equalTo: view.leftAnchor),
        scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        scrollView.rightAnchor.constraint(equalTo: view.rightAnchor)
        ])
Roman
  • 949
  • 7
  • 16
0

I tested with storyboard approach, it is working. Make sure to set the scrollView & it's subviews Constraints.

Here with the test sample XCode project for references: https://github.com/DazChong/LargeTitleShrinkOnScrollView

DazChong
  • 3,513
  • 27
  • 23
  • @HabibAli, before your downvote, have you tested with latest iOS 11.2? I tested on various devices it's indeed working by fresh git clone. – DazChong Jan 13 '18 at 04:02
  • @HabibAli and it even working for iOS 11.0.1, since Apple do not longer allow download of iOS 11.0 simulators. Please update your system to the latest before judge. – DazChong Jan 13 '18 at 04:11
  • 1
    it is not allowing me to upvote this, unless you edit your answer. Please edit your answer and tell peoples to use latest xcode with 11.2 simulators – Habib Ali Feb 05 '18 at 18:10
0

If u have problem collapse root navigationBar from presented VC's scrollView, can try this: Add UIScrollView to RootVC. Important - UIScrollView must be root view(at index 0). In presentedVC we can use scrollViewDidScroll and set offset on our root UIScrollView. see here with gif and code RootVC view hierarchy: screenshot (sorry for link, not enough reputation)

in presentedVC try this

func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if #available(iOS 11.0, *) {
            let offset = scrollView.contentOffset
            rootVC.scrollView.contentOffset = offset
        }
}
vanab
  • 1
  • 1
  • 3
-1

You need to:

  1. Pin scroll top view to super view top

  2. Add

override func viewDidLoad() {
    super.viewDidLoad()

    scrollView?.alwaysBounceVertical = true
  }
talsharonts
  • 267
  • 1
  • 9