Ruler of iOS apps - UIScrollView

Premise

What does a Product listing, Shopping cart, Orders list, Chat messages' list, Contacts list, Maps, etc have in common? They are all scrollable content: content that can't fit on the screen at once. All these are built using one underlying ui component: UIScrollView.

UIScrollView

Apple's documentation states that a UIScrollView is:

A view that allows scrolling and zooming of its contained views.

That's a pretty good summary about this ui component. I call it, the ruler of apps. See. Handheld devices have quite a small screen real estate unlike lap or desk held devices such as your Macbook or iMac. And thus content has to be crunched to fit and be displayed efficiently and for quick consumption.

This view is so conveniently named since, in its main use case, the content to be displayed is much larger than it's bounds (usually covering the whole device screen) and thus it needs to be scrolled to view the rest of the content. (Technically, it also supports a zooming gesture also but we won't discuss that here.)

scroll_view.001.png

As shown in the diagram above, the visible content rectangle corresponds to the scroll view's bounds. Any content outside this region is not drawn. To view the clipped content, you scroll up or down. (I've put cell-like content that simulates a table or collection view but you can put any content you desire, sized however you like, say a map or a huge image that you can scroll horizontally and/or vertically).

scroll_view.gif

Let's take a look at some of the frequently used properties of a scroll view.

Content Size

Question. How does the scroll view know how big its scrollable content is? contentSize. This is a CGSize typed property on the scroll view that is very fundamental to its functionality. To denote when the scroll view's content has been scrolled all the way downward (and rightward if allowing horizontal scrolling), the scroll view's bounds origin is at point .zero. On the other hand, the furthest you can scroll upward (and leftward, if allowing horizontal scrolling) is governed by the contentSize. And thus this property gives us the limits of scrolling. I like to think of the scrollable portion of the scroll view being determined by the rect CGRect(origin: .zero, size: contentSize).

contentsize.001.png

Two scenarios can be spun from this.

  1. When the contentSize is smaller than or equal to the scroll view's bounds (non-scrollable 😎)

  2. When the contentSize is larger than the scroll view's bounds

Non-scrollable

When the content size is less than or equal to the scroll view's bounds, there's nothing to scroll -- all content is visible within the bounds.

fit.001.jpeg

Scrollable

When the content size is larger than the scroll view's bounds, scrolling is required to be able to view all content. The animation below shows scrolling to origin point .zero and then to the furthest scroll possible represented by the contentSize.

pan.gif

Content Inset

The contentInset give us a margin around the scroll view's content. Say we add edge insets of 100 on either side of the scroll view, the resulting setup is as shown below. Note that the subview is still positioned relative to the scroll view's origin CGRect(x: 0, y: 0, width: 100, height: 100) but appears to have a 100 units padding from the top and left edges of the scroll view. This is as a result of giving the scroll view content inset.

scrollviewinsets.png

Here's the source for the above hierarchy.

Adjusted Content Inset

I had not become fond of adjustedContentInset property until recently when I had to account for the safe area of an edge-to-edge scroll view. I learnt about safe area propagation in the view hierarchy from Matt Neuburg's Programming iOS 13 book. I highly recommend this book.

As the name of the property connotes, this value accounts for the content insets that have been adjusted. "Adjusted by what?", you may ask. Safe area insets. This value can be thought to be expressed as: adjustedContentInset = contentInset + safeAreaInset.

Looking at the above relation, adjusted content inset would be relatively easy to calculate and no need for an extra property on the scroll view. It's there because there's more. There's another property that controls this value -- specifically the safe area inset part: contentInsetAdjustmentBehavior. Lucky for us, the resulting adjusted insets are calculated automatically with respect to the content inset adjustment behavior. This behavior property takes one of the options enumerated in the documentation (.always, .never, .scrollableAxes, or .automatic) to alter the final value of the adjustedContentInset. In summary, you choose any of these options to configure how you want the safe area insets to adjust your scroll view's content inset.

Content Offset

This is a CGPoint property that denotes the current scroll position. Its range is governed by the scrollable portion of the scroll view mentioned earlier: CGRect(origin: .zero, size: contentSize).

Scroll View Delegate

Ah delegates! I guess my most love-to-hate architectural decision by Apple. Whenever I switch from other frameworks and work with iOS, I'm always fond of looking for which delegates are available for me to explore. Usually there's one or sometimes, a protocol-chain of delegates.

The scroll view's delegate is a class adopting the UIScrollViewDelegate protocol. There're a handful of methods for you to explore but the one method I've kinda always used is the scrollViewDidScroll(_:). This method tells you that the scroll view has scrolled. In most scenarios, this method is called several times hence a guard statement or some code that executes fast is necessary not to do too much processing during scrolls. To detect when dragging will begin, end and so on, check out Apple's documentation on other delegate methods available.

Static subviews

I recently had to implement a feature where a view was to stay stationary in relation to the scroll view's content scrolling. Upon some digging, I found out about the scroll view's frameLayoutGuide. This property is controlled by the scroll view's frame and serves as a great way to create static subviews positioned wherever you'd like on the scroll view's frame.

Subclasses

Some of the main scroll view's subclasses you might be familiar with are the well known UITableView and it's superior cousin, UICollectionView. These classes build upon the basics discussed above (and in the documentation) to give a great developer experience in presenting scrollable content. They are also a great source of iOS interview questions 😎.

Conclusion

We've just scratched the surface on working with a scroll view. As hinted, it forms the basis of many iOS app designs and development. The basic premise of scrollable (and zoomable) functionality fits well with the paradigm of providing an entry point to seemingly endless content. Many apps heavily use a scroll view or any of its subclasses to present content to users. In one way or the other, you'll work with this view in your development lifetime: embrace it. Let it rule. Happy coding!

References

No Comments Yet