转自出处:http://oleb.net/blog/2014/04/understanding-uiscrollview/
Understanding UIScrollView
I am a big fan of Mike Ash’s Let’s Build … series of articles, in which he explains how certain Cocoa features work by rebuilding them from scratch. In this post, I am gonna try to do something similar by writing my own little scroll view with just a few lines of code.
But first, let’s take a closer look how coordinate systems work in UIKit. Feel free to skip the next section if you are only interested in the scroll view implementation.
Coordinate Systems
Every view defines its own coordinate system. It looks like this, with the x-axis pointing to the right and the y-axis pointing down:
Note that this logical coordinate system does not concern itself with the width and height of the view. It has no boundaries and extends infinitely in all four directions.1 Now let’s lay out a few items (a.k.a. subviews) in this coordinate system. Each colored rectangle stands for a subview:
In code, the setup would look like this:
Bounds
The UIView
documentation says this about the bounds
property:
The bounds rectangle … describes the view’s location and size in its own coordinate system.
A view can be considered a window or viewport into a rectangular area of the plane defined by its coordinate system. And the view’s bounds
express the location and size of this rectangle.
Say our view’s bounds
rectangle has a width and height of 320 by 480 points and its origin is the default(0, 0)
. The view becomes a viewport into the coordinate system plane, displaying a small part of the entire plane. Everything outside the bounds is still there, only hidden:2
A view provides a viewport into the plane defined by its coordinate system. The view’s bounds rectangle describe the position and size of the visible area.
Frame
Next, we will modify the origin of the bounds rectangle:
The origin of the bounds rectangle is now at (0, 100)
so our scene looks like this:
It looks as though the view has moved down by 100 points, and this is in fact true in relation to its own coordinate system. The view’s actual position on the screen (or in its superview, to put it more accurately) remains fixed, however, as that is determined by its frame
, which has not changed:
The frame rectangle … describes the view’s location and size in its superview’s coordinate system.
Since the view’s position is fixed (from its own perspective), think of the coordinate system plane as a piece of transparent film we can drag around, and of the view as a fixed window we are looking through. Adjusting the bounds
’s origin is equivalent to moving the transparent film such that another part of it becomes visible through the view:
Modifying the origin of the bounds rectangle is equivalent to moving the coordinate system in the opposite direction while the view’s position remains fixed because its frame does not change.
And this is exactly what UIScrollView
does when it scrolls. Note that from the perspective of the user it appears as though the view’s subviews are moving, although their positions in terms of the view’s coordinate system (in other words, their frames) remain unchanged.
Let’s Build UIScrollView
A scroll view does not need to constantly update the coordinates of its subviews to make them scroll. All it has to do is adjust the origin of its bounds. With that knowledge, implementing a very simple scroll view is trivial. We set up a gesture recognizer to detect the user’s pan gestures, and in response to a gesture we translate the view’s bounds
by the dragged amount:
Just like the real UIScrollView
, our class has a contentSize
property that must be set from the outside to define the extent of the scrollable area. When we adjust the bounds, we make sure to only allow valid values.
The result:
Our custom scroll view in action. Note that it lacks momentum scrolling, bouncing, and scroll indicators.
Conclusion
Thanks to the nested nature of coordinate systems in UIKit, it takes less than 30 lines of code to reimplement the essence of UIScrollView
. Of course, there is a lot more to the real UIScrollView
than just this. Momentum scrolling, bouncing, scroll indicators, zooming, and delegate methods are just some of the features we have not implemented here.
Update May 2, 2014: The code is now available on GitHub.
Update May 8, 2014: Check out the follow-up post for added features like inertial scrolling, bouncing, and rubber-banding.