Recently, I had time to realize more functions of pull-up refresh and pull-down loading in tableView, and found my blind spots and deficiencies in basic knowledge. By the way, I took out the code to share with you. There’s a GitHub portal at the end of the article.

1. Important concepts of bounds and frame

Frame: Position and size of the view in the parent view coordinate system. (The reference point is my father’s coordinate system.)

Bounds: The position and size of the view in the local coordinate system. (the reference point is the local coordinate system, which is the View’s own coordinate system, starting at 0,0.)

ContentSize: The scope of the scroll view, that is, the size of all the content

ContentOffset: The point at which the origin of the content view is offset from the origin of the scroll view. The height difference between the content area and the frame area of the ScrollView. Note that it must be a frame, as explained below.

ContentInset: If you want additional recommendations on this. The distance that The content view is inset from The recommendations Scroll view. When a contentInset is present it makes the user feel that the content is some distance away from the border, but it doesn’t take up the added margin content, and the hidden view is visible, which will be used later in the code. (See Figure 2)

A good understanding of bounds is that it is a layer of bounds that floats above the frame and displays the content, except that it is the boundary and the contentView is the content in the middle. Normally, a View’s bounds start at the origin of (0,0) because it uses its own coordinate system, but if you change the origin of its bounds, all the views of the View are arranged at the changed origin. To sum up, the origin of the bounds affects the display position of its subviews.

These concepts are so important that I suggest you write a demo to see them in action.

Recommend a good article about this introduction: 👉iOS View Frame and bounds difference, setBounds use (delve into) – Guo Xiaodong’s column – CSDN blog

Key concepts of tableView

The contents of the TableView include:

1.cell

2.sectionHeader / sectionFooter

3.tableHeaderView / tableFooterView

Copy the code

Y == the difference between the top of contentoffset. y and frame.origin

insetIs tightly glued to the content, if anytableHeaderView / tableFooterView, will be displayed directly above/below it (see Figure 1)

FIG. 1

I mentioned earlier in Part 1ContentInset will make the hidden view appear, please refer to Figure 2 for details

Figure 2

Three, pull down refresh logic analysis

The iPhoneX and iPhone8 models are compatible

Here are the attributes used:

@property (nonatomic,assign) NSInteger dataCount;

/** Drop down refresh control */

@property (nonatomic, weak) UIView *header;

/** Drop down to refresh label*/

@property (nonatomic, weak) UILabel *headerLabel;

/** Drop down to refresh the H state */

@property (nonatomic, assign, getter=isHeaderRefreshing) BOOL headerRefreshing;



/** Pull up refresh control */

@property (nonatomic, weak) UIView *footer;

/** Pull up to refresh label*/

@property (nonatomic, weak) UILabel *footerLabel;

/** Pull up the refresh h state */

@property (nonatomic, assign, getter=isFooterRefreshing) BOOL footerRefreshing;

Copy the code

First, initialize the refresh controls in the header and bottom. Considering that there are advertisements or search boxes in the header of general applications, the header of the refresh status display is directly attached to the content addition in the form of Add subview, that is, on the top of the advertisement bar.

// Set the banner and other controls through headerView

UILabel *adLab = [[UILabel alloc] init];

adLab.backgroundColor = [UIColor redColor];

adLab.text = @"I am advertising";

adLab.frame = CGRectMake(0, 0, 0, 30);

adLab.textAlignment = NSTextAlignmentCenter;

self.tableView.tableHeaderView = adLab;



// Set the drop-down refresh control

UIView *header = [[UIView alloc] init];

header.frame = CGRectMake(0, -50, self.tableView.bounds.size.width, 50);

header.backgroundColor = [UIColor blueColor];

self.header = header;

[self.tableView addSubview:header];

UILabel *headerLab = [[UILabel alloc] init];

headerLab.text = @"Pull down to refresh.";

headerLab.textAlignment = NSTextAlignmentCenter;

headerLab.frame = header.bounds;

self.headerLabel = headerLab;

[self.header addSubview:headerLab];

Copy the code

There’s nothing special at the bottom of the footer, so you can define a view that you want to display and just copy it to tableFooterView, so I’m not going to show it here.

The next step is to listen for changes in the header position to change the text in the header, and of course the image can also change, add the image and rotate it when appropriate. I’m calling dealHeader and dealFooter in the scrollViewDidScroll method.

Pragma mark - ScrollView proxy method

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{

    [self dealHeader];

    [self dealFooter];

}

Copy the code

The processing of the header is analyzed.

Pragma mark - A method for handling changes to the drop-down refresh control

- (void)dealHeader{

// Returns directly if you are in a drop-down refresh

    if (self.isHeaderRefreshing) {

        return;

    }

    offsetY = -(getRectNavAndStatusHight + self.headerLabel.bounds.size.height);

    

    if (self.tableView.contentOffset.y < offsetY) {

        self.headerLabel.text = @"Release refresh now";

        self.header.backgroundColor = [UIColor grayColor];

    } else{

        self.headerLabel.text = @"Pull down to refresh.";

        self.header.backgroundColor = [UIColor blueColor];

    }

}

Copy the code

OffsetY is a static variable because it is a fixed threshold, and getRectNavAndStatusHight is a macro that gets the maximum height of the top navigation, available on models before and after the iPhoneX. OffsetY means the maximum height of the navigation bar plus the height of the header displayed in the refreshed state. The negative sign is added because the absolute value of the current contentoffset. y is negative when the drop down is greater than offsetY. Who is self judgment conditions of the code. The tableView. ContentOffset. Y < offsetY founded, the header can change the style.

A bit more about contentOffset, which you see in Figure 2, is getting smaller and smaller as the fingers slide down (that is, they drop down) until they become negative and larger as the fingers slide up. One interesting point to note here is that the initial position of contentoffset. y is -88, which is the height of the navigation bar, because the contentView is our visual area and the origin of the bounds is (0, -88), which is what I said earlier about the display of the bounds. It’s just that it’s the boundary, and the contentView is the content in the middle. I printed out the properties of the tableView’s initial state, so you can analyze it.


FIG. 3

Back to working with the header, we were able to handle the change in the header with the offset before, and then we’re able to handle it when the user drops down.

/ * *

Called when the user releases his finger

* /

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{

// Refresh only when you release your finger when the header is fully present

    if (self.tableView.contentOffset.y < offsetY) {

        [self headerBeginRefreshing];

    }

}



#pragma mark - Method to start pull-down refresh

- (void)headerBeginRefreshing{

    if (self.isHeaderRefreshing) {

        return;

    }

    self.headerLabel.text = @"Refreshing data";

    self.header.backgroundColor = [UIColor greenColor];

    self.headerRefreshing = YES;

[UIView animateWithDuration: 0.25 animations: ^ {

// Change the inner margin of the TableView so that the header stays in the user's line of sight when refreshing

        UIEdgeInsets inset = self.tableView.contentInset;

        inset.top += self.header.bounds.size.height;

        self.tableView.contentInset = inset;

    }];

// Request data

    [self loadNewData];

}

Copy the code

If the offset does not exceed the critical value, do not do the operation, just use dealHeader in scrollViewDidScroll to restore it. If the offset does not exceed the critical value, call headerBeginRefreshing. In the monitored user gestures drag-and-drop scrollViewDidEndDragging: (UIScrollView *) scrollView willDecelerate: (BOOL) decelerate method call.

The method of starting the refresh is to determine whether the refresh is in progress, and if so, directly return, mainly to prevent multiple requests to the server in the real application. In the demo, there’s no interface to request everything I’ve done with latency. The purpose of modifying contentInset is to make the header style of “requesting data” stay in the user’s view. I added an animation to make it look natural. And then you call the loadNewData method to request the data, and you just put the actual interface request in there and reloadData. At the end of the refresh, contentInset will be restored at the end of the annotations written in more detail, you can directly see the code.

So that’s the whole implementation of the drop-down refresh, but also just to get a better understanding of some of the properties of the tableView.

Four, pull up refresh logic analysis

Pragma mark - A method for handling changes to the pull-up refresh control

- (void)dealFooter{

    if (self.tableView.contentSize.height == 0) {

        return;

    }

    CGFloat footerOffset = self.tableView.contentSize.height - self.tableView.frame.size.height + kTabBarHeight_X;

    if (self.tableView.contentOffset.y >= footerOffset) {

        [self footerBeginRefreshing];

    }

}

Copy the code

The pull-up refresh process is similar to this, but I won’t go into details, but emphasize the threshold calculation: self.tableView.contentSize.height – self.tableView.frame.size.height + kTabBarHeight_X; KTabBarHeight_X is the height of the bottom navigation bar, which is also a macro that works on models before and after the iPhoneX. The height of the TabBar is added at the end because the footer is refreshed so that the top edge of the TabBar is completely exceeded. The pull up refresh logic is processed when contentoffset. y(which is the real-time offset) is greater than the critical value.

It can also be interpreted as: Subtracting the first two values is the maximum contentoffset. y of the tableView when it is displayed as contentView, but this value is just above the bottom of the tableView, which is the bottom of the TabBar, so adding the height of the TabBar is sufficient.

Fifth, the final conclusion

This article focuses on the relationship between the properties of Frame, contentSize, and contentOffset in tableView, and allows logical calculations (mainly shown in Figures 1 and 2) to perform functions similar to the head and bottom refresh in MJRefresh. Of course, I will choose to use third-party libraries in the project, but I still need to have a deep understanding of the logic inside, and I can learn a lot of things that I usually ignore.

👉Making portal