Swift 3: Always display the UITableView scrollbar

I recently had a strange request in regards to a UITableView. You see, the table in a UI could only display 3 rows before scrolling was enabled. Given the minute stature of this table, showing a user that there were more items in it was important. So it was decided that if this table had more than 3 rows in it, always display the scrollbar. The trouble with this was that Apple Human Interface Guidelines and the iOS SDK don’t allow for this behavior. One is able to flash the scrolling indicator, but that’s about it.

So it was decided that if this table had more than 3 rows in it, always display the vertical scrollbar. The trouble with this was that Apple Human Interface Guidelines and the iOS SDK don’t allow for this behavior. One is able to flash the scrolling indicator, but that’s about it.

So the problem here is twofold.

  1. Display the scrollbar upon table reload.
  2. Maintain the visible scrollbar when the table has been scrolled.

These actually aren’t too bad to nail down. You’ll want to set your showsHorizontalScrollIndicator to false. The solution below relies on only having vertical available as we’re going to loop through the table to find a UIImageView (which holds the stretching image used by the table (subclass of UIScrollView)). Set your table up normally beyond this. Make sure you’re using delegates for the table – of course, right?

How do you loop through a table to find it’s scrollbar now?

for view in self.upNextTableView.subviews
{
    if view is UIImageView
    {
        let imageView = view as? UIImageView
        imageView?.isHidden = false
        imageView?.layer.removeAllAnimations()
        imageView?.alpha = 1.0
    }
}

There you go. This is brittle as HELL. If you don’t turn off the horizontal scroll indicator, you’ll affect it t0o – and make that display in addition to the vertical. Didn’t want that. And it’s brittle because Apple could update the underpinnings of how these things are constructed and this method won’t work accurately anymore.

So this turns the scrollbar on. If a user scrolls it at all, though, it’s going to fade away again. This part is a tiny bit tricky. Since you’ve set up delegates for the table, you can implement a scroll view delegate in regards to scrolling… or namely dragging.

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    if scrollView == upNextTableView {
        if upNextTableView.numberOfRows(inSection: 0) > 3 {
            for view in self.upNextTableView.subviews
            {
                if view is UIImageView
                {
                    let imageView = view as? UIImageView
                    delay(bySeconds: 0.7) {
                        imageView?.isHidden = false
                        imageView?.layer.removeAllAnimations()
                        imageView?.alpha = 1.0
                    }
                }
            }
        }
    }
}

There is the delegate method – and because I had more than one table, I perform a check first. You probably don’t need that. Anyway, we wait 0.7 seconds before turning the scrollbar back on after a drag. If we attempt before, the animation will prevent the scrollbar alpha change to “take”. Here is the helper stuff for the delay call:

public func delay(bySeconds seconds: Double, dispatchLevel: DispatchLevel = .main, closure: @escaping () -> Void) {
    let dispatchTime = DispatchTime.now() + seconds
    dispatchLevel.dispatchQueue.asyncAfter(deadline: dispatchTime, execute: closure)
}
    
public enum DispatchLevel {
    case main, userInteractive, userInitiated, utility, background
    var dispatchQueue: DispatchQueue {
        switch self {
        case .main: return DispatchQueue.main
        case .userInteractive: return DispatchQueue.global(qos: .userInteractive)
        case .userInitiated: return DispatchQueue.global(qos: .userInitiated)
        case .utility: return DispatchQueue.global(qos: .utility)
        case .background: return DispatchQueue.global(qos: .background)
        }
    }  
}

So without using a custom scrollbar or subclassed table, you’re able to get what you need to be done. This is most of the code, but the gist of this is to show that it can be done, albeit in a very brittle way that makes me cringe.

 

3 thoughts on “Swift 3: Always display the UITableView scrollbar

Leave a Reply

Your email address will not be published. Required fields are marked *

Time limit is exhausted. Please reload CAPTCHA.

This site uses Akismet to reduce spam. Learn how your comment data is processed.