minority

First, write first

In my iOS development learning process, I have read many students’ high imitation project articles, source code, which is very helpful to me. But many of the high imitation projects in the technical aspects of each focus, so I first put this project worth discussing the technical points listed, convenient just need students.

This project focuses on:

  • UITableview performance optimization
  • Advanced use of UIScrollView
  • Minority client navigation bar dynamic effect implementation
  • Multiple nested controls for UITableview
  • Manually encapsulate some common view controls

Second, the paper

First, let’s take a look at the operation effect of the project:

LYSSPai in action


Here is theLYSSPai project address.

In this article, I will first introduce the overall implementation of the project, and then further discuss the points I encountered during the development process that are worth discussing.

The data in the project is obtained by using Charles capture and stored in the bundle with JSON files. The source of the material in the project is the official client IPA package obtained by parsing iOS Images Extractors. Disclaimer: Only for study and exchange, strictly prohibited for commercial use.

Third, the overall implementation idea

In this section, I’ll break down the overall development idea by page.

1. The first page

Home page Display-1


Home display -2

1.1 Page Overview

  • This is the front page of the project. The main structure is the navigation bar at the top and the content below.
  • Navigation bar effect: When the page slides up, the text and button size of the top navigation bar will decrease dynamically, and then move up as a whole and hover at the top, simulating the navigation bar effect of the system. When the page slides, the effect is reversed.
  • Content display part: First, there is a sliding left and right part similar to the wheel broadcast chart. It is used to display key recommended topics, articles, advertisements, etc. Here’s an article. And then another manual slide similar to the wheel map. Used to display paid columns. The rest is all articles.

1.2 Implementation Roadmap

1.2.1 Content Display

Using UITableview, there are three types of cells. In the horizontal UIScrollView, set the tag value for each sub-cell, and click the event as a delegate to the home PAGE VC. This article describes common cells. The menu button click event in the upper right corner is delegated to the home PAGE VC.

1.2.2 Navigation Bar implementation

The dynamic effects of the navigation bar need to slide along with the content, then hover over the top. This involves navigation bar height changes and hover effects. Tableheaders don’t have a hover effect on the top, but you can easily change the height of the view:

CGRect newFrame = headerView.frame; newFrame.size.height = newFrame.size.height + webView.frame.size.height; headerView.frame = newFrame; // The beginUpdates and endUpdates methods are used to change the height in animated form [self.tableView beginUpdates]; // To change the tableHeader, you must call it explicitlyset[the self. The tableViewsetTableHeaderView:headerView];

[self.tableView endUpdates];Copy the code

SectionHeader is a hover effect by default, but I couldn’t find an efficient way to update the height of the view, so I abandoned it. For tableHeader hover effect, you can slide the page to the critical point, add the tableHeader to the same level of view as the TableView, manual implementation of the hover effect, this is also many UIScrollView child view to achieve the page hover effect. One thing to know, though, is that UITableView is a large object, and frequent updates to it can affect performance. And when you change the tableHeader dynamically, you’re constantly changing the layout of the entire UITableView. It doesn’t have to be this way for a small dynamic effect. So, I used a single view as the navigation bar at the top and added it to the same container scrollView as the TableView. This dynamic effect only affects the single view layout.

1.2.3 Classified thematic pages

Classified topic page


1.2.4 Article reading page

Article reading page






WKWebView

On the useWebViewShow the content discussed in my articleFrom a brief book on iOS client, talk about the detailed design of Hybrid solutionIt is discussed in detail, and you are welcome to read it.

Found 2.

Discovery page display


3. The message

Message page display


Fourth, focus on detail

1. Tableview performance optimization

  • To optimize the scene

    After page development is complete, cells are nested with scrollView, which also includes several sub-cells. If not optimized, it is expected that the user experience will not be very good. The first time I slide to the second round, I feel a drop in FPS on the page. The slide is smooth and the FPS stays around 60. So we know that optimization focuses on the first loading and rendering of the rote map. After the wheel image first appears in the screen range, it is added to the cache, so it won’t get stuck when you slide there again.

    When it comes to performance optimization, I have to recommend itibiremeIt is strongly recommended that those who have not read it read it carefullyIOS tips for keeping the interface smooth.

  • Optimization idea

    The user doesn’t feel stuck when scrolling around 60 FPS, which is the goal of optimization. In other words, we need to render each frame within 1s/60 = 16.7ms. However, view rendering requires CPU and GPU rendering. Therefore, we need to analyze the workload of CPU and GPU in this scenario and allocate them reasonably, so that the total calculation time of each frame is less than 16.7ms.

  • The reuse of the cell

    Cell reuse is a very basic but very important optimization method, correctly use the Cell reuse mechanism of TableView.

  • Cell height cache

    It will be called as many times as there are cells in the tableView rendering process- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPathMethod to determine the contentSize. Therefore, try to calculate the height of the cell in advance and cache, avoid the calculation in this proxy method, can effectively optimize the tableView rendering.

  • Layout computing optimization

    Layout calculation is the CPU’s job, and when the page level is complex, layout calculation can take a lot of time. At the same time, it should be clear that automatic layout will require CPU to complete the layout calculation, which will inevitably increase the time. Therefore, in the optimization of complex cells, it is generally recommended to manually calculate the layout to improve performance. In addition, if the page layout calculation is large, the layout calculation is done and cached before the page is rendered, which can effectively reduce the CPU computation time of 16.7ms during the view rendering. In this project, I encapsulated a frameModel for the cell of the wheel cast graph. After the page data was obtained, the layout result of the wheel cast graph was calculated in advance. When the page was rendered, the value could be directly assigned without calculation.

    / / count for cell by figure number + (instancetype) PaidNewsFrameModelWithCount: (NSInteger) count model = {PaidNewsFrameModel * [[the self alloc] init];floatCellWidth = LYScreenWidth * 0.55;floatCellHeight = LYScreenWidth * 0.7; model.cellTitleFrame = CGRectMake(25, 10, 100, 18); model.moreFrame = CGRectMake(LYScreenWidth - 65, 11, 40, 16); model.backScrollViewFrame = CGRectMake(0, 43, LYScreenWidth, cellHeight); model.paidNewsViewFrames = [[NSMutableArray alloc] init]; model.paidTitleFrames = [[NSMutableArray alloc] init]; model.avatorFrames = [[NSMutableArray alloc] init]; model.nicknameFrames = [[NSMutableArray alloc] init]; model.updateInfoFrames = [[NSMutableArray alloc] init];for ( int i = 0; i < count; i++)
      {
          NSValue *paidNewsViewFrame = [NSValue valueWithCGRect:CGRectMake(25 + (cellWidth + 15) * i, 0, cellWidth, cellHeight)];
          [model.paidNewsViewFrames addObject:paidNewsViewFrame];
          NSValue *avatorFrame = [NSValue valueWithCGRect:CGRectMake(15, cellHeight - 90, 20, 20)];
          [model.avatorFrames addObject:avatorFrame];
          NSValue *nicknameFrame = [NSValue valueWithCGRect:CGRectMake(45, cellHeight - 85, cellWidth - 75, 12)];
          [model.nicknameFrames addObject:nicknameFrame];
          NSValue *updateInfoFrame = [NSValue valueWithCGRect:CGRectMake(15, cellHeight - 50, cellWidth - 30, 12)];
          [model.updateInfoFrames addObject:updateInfoFrame];
      }
      return model;
    }Copy the code

    As you can see, with a for loop and each body of the loop having a little bit of computation, it makes sense to bring those calculations forward and execute them in child threads. We need to use that 16.7ms to our advantage.

  • Properly select the view control to slim down the view

    UIViewandCALayerWe should all know about the relationship between. UIView, which is heavier than CALayer, encapsulates the parts of the interaction. If the current control does not need to respond to user actions, we should use CALayer instead of UIView whenever possible.

    In this project, in the part of paid content rotation graph, the whole sub-cell needs to respond to user’s click operation. So you only need to add gesture recognition to the view at the bottom of the child cell. Elements such as background images and user avatars do not need to respond to special actions, so these controls are not usedUIImageView, switch toCALayer. In fact, the text part, also can not useUILabelThis is the part that can be further optimized.

    Here’s the layout code for the avatar section:

    CALayer *avator = [[CALayer alloc] init];
    [paidNewsView.layer addSublayer:avator];
    NSValue *avatorFrame = self.model.paidNewsFrame.avatorFrames[i];
    avator.frame = avatorFrame.CGRectValue;
    [avator yy_setImageWithURL:[NSURL URLWithString:self.model.PaidNewsData[i][@"avatar"]] placeholder:nil options:kNilOptions progress:nil transform:^UIImage * _Nullable(UIImage * _Nonnull image, NSURL * _Nonnull url) {
      image = [image yy_imageByRoundCornerRadius:40.0];
      return image;
    } completion:nil];Copy the code
  • Network content is loaded asynchronously

    After the page is displayed, the web content loads slowly, which is also a good use of time.

    Asynchronous loading network picture frame, we are all familiar withSDWebImage, there areibiremetheYYWebImage. It is reported that YYWebImage’s performance is better than SD, which I have not personally verified.

    Here I use YYWebImage:

    [avator yy_setImageWithURL:[NSURL URLWithString:self.model.PaidNewsData[i][@"avatar"]] placeholder:nil options:kNilOptions progress:nil transform:^UIImage * _Nullable(UIImage * _Nonnull image, NSURL * _Nonnull url) {
      image = [image yy_imageByRoundCornerRadius:40.0];
      return image;
    } completion:nil];Copy the code
  • The rounded set

    Again, the usual round corners. Using CALayer’s properties to implement rounded corners triggers off-screen rendering and increases GPU workload.In this optimization, you can use the CPU to cut the image material directly to the rounded image and then display it. Of course, the best solution is to have your artists provide the rounded corners directly

    I’m going to use it directly hereYYImageFillet processing.

2. Advanced use of UIScrollView

This part I mainly talk about the idea of message page selector control encapsulation. First look at the effect:

SelectView effect display



But there is one detail to note: when you swipe left or right with the swipe gesture, the page must scroll. When drag is used, the drag range is determined to determine whether to scroll.



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


/ / stop dragging agent - (void) scrollViewDidEndDragging: (UIScrollView *) scrollView willDecelerate: (BOOL) decelerate {/ / if it is transverse sliding content pageif (scrollView == self.contentView)
    {
        NSLog(@"slowing?? % @",decelerate ? @"YES" : @"NO"); CGFloat scrollX = scrollView.contentOffset.x; // If there is inertia (fast sliding), the content page must move accordinglyif (decelerate)
        {
            if (self.selectedTag == 0 && scrollView.contentOffset.x > 0)
            {
                self.selectedTag = 1;
            }
            else if(self.selectedTag == 1 && scrollView.contentOffset.x < LYScreenWidth) { self.selectedTag = 0; }} // If there is no inertia (slow drag), then the drag range will be satisfied before movingelse
        {
            if(self.selectedTag == 0 && scrollX >= 0.5 * LYScreenWidth) {self.selectedTag = 1; }else if(self.selectedTag == 1 && scrollX <= 0.5 * LYScreenWidth){self.selectedTag = 0; } } [self contentViewScrollAnimation]; }}Copy the code

When the page is swiped, the ScrollView has inertia, and when the page is dragged, it has no inertia. Use this property to make judgments accordingly. Here’s an animation of the bar moving:

/ / content for mobile encapsulation - (void) contentViewScrollAnimation {/ / according to the selected button to calculate the offset of contentView CGFloat offsetX = self. SelectedTag * LYScreenWidth; CGPoint scrPoint = self.contentView.contentOffset; scrPoint.x = offsetX; UIView animateWithDuration:0.3 animations:^{[self.contentViewsetContentOffset:scrPoint]; }]; / / notification selector, small horizontal movement [self. SelectView selectBtnChangedTo: self. SelectedTag]; }Copy the code

3. Realization of dynamic effect of navigation bar

Let’s look at the effect again:

Navigation bar effect display



- (void)scrollViewDidScroll:(UIScrollView *)scrollView


// The scrollView has just started to slide, at which point the navigation title size and button size changeif(Y <= -97&&y > -130) {// The critical Y values calculated with the fonts 36 and 20 are -97 and -130, CGFloat fontSize = (-(((16.0 * Y)/33.0)) -892.0/33.0; self.titleLabel.font = [UIFont fontWithName:@"HelveticaNeue-Bold" size:fontSize];
        //            NSLog(@"point:: %f",self.titleLabel.font.pointSize); // Update titlelabel height constraints [self. titlelabel mas_updateConstraints:^(MASConstraintMaker *make) { Make. Height. Mas_equalTo (self. TitleLabel. The font. PointSize + 0.5);}]; / / computing at the moment the button of the corresponding size, if it is greater than the minimum value (16), then update the constraint CGFloat buttonSize = self. The titleLabel. The font, pointSize * (5.0/9.0);if(buttonSize >= 0) [self.button mas_updateConstraints:^(MASConstraintMaker *make) { make.width.mas_equalTo(buttonSize); make.height.mas_equalTo(buttonSize); }]; }Copy the code

It’s a little bit complicated here, so you can take a look at it.

4. Multiple controls nested in UITableview

This part has been briefly covered in the page implementation section of the previous article, and is listed here to remind beginners to pay attention to it.

5. Manually encapsulate some common view controls

In this project, I encapsulated the navigation view HeaderView, the selector view SelectView and the loading view LYLoadingView of the page. Those who need to know can take a look at some. Here is a brief demonstration of the encapsulation of the Loading view. This is the header section:

@interface LYLoadingView: UIView // Hide incoming view loadingView + (BOOL)hideLoadingViewFromView:(UIView *)view; // display a loadingview + (BOOL)showLoadingViewToView:(UIView *)view WithFrame:(CGRect)frame; @endCopy the code

This is the implementation:

+ (BOOL)hideLoadingViewFromView:(UIView *)view
{
    NSEnumerator *subviewsEnum = [view.subviews reverseObjectEnumerator];
    for (UIView *subview in subviewsEnum)
    {
        if([subview isKindOfClass:self])
        {
            [subview removeFromSuperview];
            returnYES; }}return NO;
}

+ (BOOL)showLoadingViewToView:(UIView *)view WithFrame:(CGRect)frame
{
    LYLoadingView *loadingView = [[LYLoadingView alloc] initWithFrame:frame];
    loadingView.backgroundColor = [UIColor whiteColor];
    UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
    indicator.center = CGPointMake(frame.size.width/2, frame.size.height/2 - 100);
    [indicator startAnimating];
    [loadingView addSubview:indicator];
    [view addSubview:loadingView];
    return YES;
}Copy the code

The Loading view mimics a simple chrysanthemum indicator in the official app. To use loadingView, add a loadingView to the view at the beginning of the page rendering:

// Initialize loadingView CGRect loadingViewFrame = CGRectMake(0, 130, LYScreenWidth, Lyscreenheight-130); [LYLoadingView showLoadingViewToView:self.view WithFrame:loadingViewFrame];Copy the code

After page data is obtained, the table is reload, and then the loading view is removed:

[self.newsTableView reloadData]; / / hide loadingview [LYLoadingView hideLoadingViewFromView: self view];Copy the code

Write at the end

The project did not fully recover the official client, and the author did not have time to do so, so I ended it in a hurry and wrote this article for the end. There are still some bugs and unfinished feature points in the project, welcome to fork. You are welcome to point out any shortcomings, and also welcome to discuss other implementation methods in the project, hoping to help students in need.

One last pasteLYSSPai project address. If you feel good, I hope to click star~

halo