6

When using large titles and tapping the status bar to scroll to the top of a UIScrollView or UITableView (probably also UICollectionView, haven't tested this) it always goes a little too far.

I have refresh enabled on my TableView and when tapping the status bar it appears like this and stays that way until I tap the screen.

enter image description here

I have a ScrollView in another ViewController and if I tap the status bar there it also scrolls a little bit too far, making the navigation bar too tall. This also returns to normal when I tap somewhere or scroll a tiny bit.

Normal: enter image description here

After I tapped the status bar: enter image description here

This also only happens when I have large titles activated, using normal titles everything works as it should.

Any ideas how to fix this?

How to recreate:

  1. Create a new project with a navigation controller and a UIViewController with a TableView inside.
  2. Set navigation controller to prefer large titles. Turn translucent off. Set title on UIViewController
  3. Set constraints on TableView to pin to the edges of the ViewController
  4. Create outlet for TableView in the ViewController
  5. Implement delegates and set a number of rows, for example 100
  6. Launch app
  7. Scroll down so the large title becomes a normal title
  8. Tap status bar so the tableView scrolls to the top

Now the title is not at the position it should be, if you now scroll a tiny bit up or down it snaps back to the normal position.

ViewController code:

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.delegate = self
        tableView.dataSource = self
    }

}

extension ViewController: UITableViewDelegate, UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 100
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "TestCell", for: indexPath)

        return cell
    }

}
Bram-N
  • 426
  • 4
  • 15
  • I created a new project and put large titles on and added a refresh control added 100 items in the TableView and I couldn't reproduce your bug. – Serj May 31 '18 at 13:28
  • @Serj oh weird, it's not that I did a lot in my viewcontrollers that could cause this, I'll try to find what I did wrong, thanks! – Bram-N May 31 '18 at 13:45
  • 1
    If you could provide us with a way to reproduce the bug we would be more than happy to help. Here is a guideline on how to do that https://stackoverflow.com/help/mcve – Serj May 31 '18 at 13:50
  • @Serj added the steps to recreate, did it from scratch and I still have the same problem – Bram-N May 31 '18 at 14:18
  • Can you show your code for your table view – Fogmeister May 31 '18 at 14:20
  • 1
    @Fogmeister added (for the new project from scratch since it also occurs there, it's just the basic code though) – Bram-N May 31 '18 at 14:21

6 Answers6

10

Okay so I found why the problem occurs, but not how to fix it in that exact scenario.

If you're using large titles and a UITableViewController with the navigation bar translucency set to off the problem will occur. When you turn translucent back on the problem goes away.

If you're using a TableView in a normal UIViewController the problem always occurs.

Edit

Turns out setting "extendedLayoutIncludesOpaqueBars = true" fixes the problem if you're using a translucent navigation bar!

Similar question: UIRefreshControl() in iOS 11 Glitchy effect

Bram-N
  • 426
  • 4
  • 15
3

I faced the same issue. I had a collectionview in UIViewController embedded in UINavigationController. Collectionview had leading, trailing, top, bottom constraints to a safe area.

To fix this you need:

  1. Change the top constraint of collectionview to supervises (NOT safe area)
  2. Set extendedLayoutIncludesOpaqueBars = true in viewDidLoad method
Viktoria
  • 119
  • 6
  • Fixes the issue somewhat but complete solution was just: `self.tableView.scrollRectToVisible(CGRect(x: 0.0, y: 0.0, width: 1, height: 1), animated: true)` – Codetard Aug 11 '20 at 10:48
1

After many hours of tests i found a solution that works with UIViewController.

In UIViewController with UITableView, you should:

  • in viewDidLoad set extendedLayoutIncludesOpaqueBars = true
  • in storyboard pin tableView top constraint to superview top with constant = 0 (tableView will be under navigationbar and statusbar when navigationbar is translucent)

After that if you tap on statusbar, tableview stops in the right place.

enter image description here

pawel_d
  • 487
  • 5
  • 9
  • Have you checked with iPhone X? I needed to set the tableView top constraint against the safe area. – Frederic Adda Nov 18 '18 at 13:26
  • @FrédéricAdda Yes and it works. It works also with not translucent navigationbar (check my edited answer). Why you need top constraint in safe area? – pawel_d Nov 19 '18 at 10:23
  • in my case, I need to pull the tableView to reload. If I use "prefers large font" in the navigationBar, the tableView doesn't scroll to the top. If I use small fonts, then I have no issue. – Frederic Adda Nov 19 '18 at 15:35
  • @FrédéricAdda In my scenario i also have UIRefreshControl and works well. I can't help you without code. Please also remember that with largeTitle, you need set your refreshControl to tableView.refreshControl instead of tableView.addSubview(refreshControl) – pawel_d Nov 19 '18 at 16:02
  • This code seems to work fine, but what would you do if you didn't want the TableView constrained to the Superview, but to another View which changes position depending on the navigaitionbar size? – Gary Apr 06 '20 at 10:20
1

In your ViewController declare a var didBeginScrollToTop of Bool type. Assign the controller as scrollView's delegate. Then

func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
    didBeginScrollToTop = true
    return true
}

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    guard didBeginScrollToTop, scrollToTopGestureTargetView?.contentOffset.y ?? 0 < -25 else { return }

    scrollToTopGestureTargetView?.setContentOffset(.zero, animated: true)
}

func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
    didBeginScrollToTop = false
}

What this does is adjusting your content offset when bouncing. The flag is there to catch the scrollToTop gesture and is reset on scroll. You can also set it off after setting the .zero offset. If you have implementation with child VCs, don't forget to call super when overriding those 3 delegate methods. Now my first approach was to track when the content offSet is < 0, but on some devices it did not expand the navigation item and that magic -25 seemed to work on all simulators. You can combine this approach with an implementation that returns the size of the nav bar and replace it, but as far as I am aware there wasn't an easy way to get the nav bar frame/bounds so easily.

Note you may have to handle also this: Strange velocity prefers large title

0

For me remove checkmark from 'Under Top Bars' in storyboard controller fix this issue.

Dmitry
  • 1
  • 2
-1

I've fix it with maually setting the contentOffset to 0 after scroll to top.

func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
    return true
}

func scrollViewDidScrollToTop(_ scrollView: UIScrollView) {
    scrollView.contentOffset.y = 0.0
}
avgx
  • 644
  • 5
  • 8
  • Your solution seems less hacky compared to all other i saw before. Does it work well? No animation glitches? – Mikael May 23 '19 at 09:27
  • 1
    It's not ideal, but working. I mean after the animation stops everything is ok, but the animation is not so fast, so it's visible that something is ugly. – avgx May 24 '19 at 10:47
  • This is pretty janky. After like a full second it just snaps back and collapses my search controller search bar. (iOS 13 Xcode 11.1). Can't recommend this – PJayRushton Oct 31 '19 at 05:26