First of all, before we talk about UIScrollView, let’s look at how coordinate systems work in UIKit.

Coordinate system

Because eachviewDefines its own coordinate system, which looks something like this: the X-axis points to the right and the Y-axis points down (as shown below).

Note, however, that this logical coordinate system is independent of the width and height of the view. It has no boundaries and stretches indefinitely in all four directions (infinite stretches are not really infinite, since the range of the coordinate system is limited by the size of the CGFloat data type to 32 bits on 32-bit systems and 64 bits on 64-bit). Now let’s put some views in the coordinate system. Each rectangle with a different color represents a subview:

The actual code looks like this:

UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(20, 20, 100, 100)]; Redview. backgroundColor = [UIColor colorWithRed:0.815 green:0.007 blue:0.105 alpha:1]; UIView *greenView = [[UIView alloc] initWithFrame:CGRectMake(150, 160, 150, 200)]; GreenView. BackgroundColor = [UIColor colorWithRed: green 0.494:0.827 blue: alpha 0.129:1); UIView *blueView = [[UIView alloc] initWithFrame:CGRectMake(40, 400, 200, 150)]; BackgroundColor = [UIColor colorWithRed:0.29 green:0.564 blue:0.886 alpha:1]; UIView *yellowView = [[UIView alloc] initWithFrame:CGRectMake(100, 600, 180, 150)]; YellowView. BackgroundColor = [UIColor colorWithRed: green 0.972:0.905 blue: alpha 0.109:1); [mainView addSubview:redView]; [mainView addSubview:greenView]; [mainView addSubview:blueView]; [mainView addSubview:yellowView];Copy the code

To talk about views is to talk about bounds and frame, and then to talk briefly about bounds and frame.

Bounds

For Apple, the view bounds are explained as follows:

A bounding rectangle that describes the position and size of the view in its own coordinate system.

A view can be thought of as a window or viewport into a flat rectangular region defined by its coordinate system. And the view Bounds expresses the position and size of the rectangle.

Suppose our view’s bounds rectangle is 320 x 480 points wide and high, and its origin is the default (0, 0). The view becomes the viewport of the coordinate system plane, displaying a small portion of the entire plane. Everything outside of bounds is still there, just hidden. In fact, subviews outside the bounding rectangle will remain visible unless clipsToBounds == YES (default: NO). However, the view does not detect touches beyond its boundaries.

Next, we will modify the origin of the bounding rectangle:

CGRect bounds = mainView.bounds;
bounds.origin = CGPointMake(0, 100);
mainView.bounds = bounds;
Copy the code

The origin of the bounding rectangle is now at (0, 100) so our scene looks like this:

It looks like the view has moved down 100 points, but it actually has to do with its own coordinate system. The view’s actual position on the screen (or, more precisely, in its parent view) remains fixed, unchanged, because it is determined by its frame.

The above picture coordinates are the coordinate system drawn for your own view.

Because the view’s position is fixed (from its own point of view), think of the coordinate plane as a canvas we can drag around, and think of the view as a transparent glass. Adjusting the origin of the bounds is equivalent to moving the canvas so that another part of it is visible through the view. This will make it look like the picture below:

That’s exactly how UIScrollView works when it scrolls. But it appears from the user’s perception that the view’s subviews are moving, even though their position in terms of the view coordinate system (in other words, their frame) remains the same. We can also use this principle to write a simple UIScrollView (because the system’s UIScrollView involves momentum scrolling, bounce, scroll indicators, scaling and delegate methods, etc.).

Custom UIScrollView

A scroll view does not need to constantly update the coordinates of its children to make them scroll. All it has to do is adjust the origin of its boundary. This makes implementing a very simple scrolling view seem trivial. We set up a gesture recognizer to detect the user’s pan gesture, and in response to the gesture, we bounds drag momentum to pan the view:

// CustomScrollView.h
@import UIKit;

@interface CustomScrollView : UIView

@property (nonatomic) CGSize contentSize;

@end

// CustomScrollView.m
#import "CustomScrollView.h"

@implementation CustomScrollView

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self == nil) {
        return nil;
    }
    UIPanGestureRecognizer *gestureRecognizer = [[UIPanGestureRecognizer alloc]
        initWithTarget:self action:@selector(handlePanGesture:)];
    [self addGestureRecognizer:gestureRecognizer];
    return self;
}

- (void)handlePanGesture:(UIPanGestureRecognizer *)gestureRecognizer
{
    CGPoint translation = [gestureRecognizer translationInView:self];
    CGRect bounds = self.bounds;

    // Translate the view's bounds, but do not permit values that would violate contentSize
    CGFloat newBoundsOriginX = bounds.origin.x - translation.x;
    CGFloat minBoundsOriginX = 0.0;
    CGFloat maxBoundsOriginX = self.contentSize.width - bounds.size.width;
    bounds.origin.x = fmax(minBoundsOriginX, fmin(newBoundsOriginX, maxBoundsOriginX));

    CGFloat newBoundsOriginY = bounds.origin.y - translation.y;
    CGFloat minBoundsOriginY = 0.0;
    CGFloat maxBoundsOriginY = self.contentSize.height - bounds.size.height;
    bounds.origin.y = fmax(minBoundsOriginY, fmin(newBoundsOriginY, maxBoundsOriginY));

    self.bounds = bounds;
    [gestureRecognizer setTranslation:CGPointZero inView:self];
}

@end
Copy the code

Just like the real UIScrollView, our class has a property that contentSize must set from the outside to define the scope of the scrollable area. When we adjust the bounds, we make sure that only valid values are allowed. A run like this looks like this:

Frame

Frame rectangle that describes the position and size of a view in its parent view’s coordinate system.

Because this article is about UIScrollView, Frame and the difference between Frame and Bounds will be discussed in detail in the next part.