Understand how UITableview works by implementing a horizontal Tableview

author coderZ

Introduction of UITableview Agent Method

UITableview has two related agents: UITableViewDelegate, UITableViewDataSource
Data Source is the data source agent and delegate is the related operation agent.

dataSource

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

By returning a value, tell the tableview how many cells a section should display

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

By returning a value, tell the tableview that the cell height under the indexPath index is the same as the width of the tableview cell.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

By returning the cell, tell the tableview that the cell should be displayed under the indexPath index
These are the three basic and necessary agents of tableview dataSource, through which a basic tableview can be presented.

delegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

Click Cell Callback Method

UITableViewCell and Reuse

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
    if (cell == nil) {
        UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
    }
    return cell;
}

The above is the basic way to reuse tableview. By creating a cell with a reuse identifier, each time a tableview gets a cell through a proxy, it first gets it from the reuse pool, saving memory consumption. These are the most basic proxy methods of tableview. Here, we implement these proxy methods by code to get a glimpse of the inner working principle of tableview.

Implementing a horizontal tableview: MinScrollMenu

ps: Source code has been uploaded to github: MinScrollMenu

introduce.gif

1 Definition agent

- (NSInteger)numberOfMenuCount:(MinScrollMenu *)menu;
- (CGFloat)scrollMenu:(MinScrollMenu*)menu widthForItemAtIndex:(NSInteger)index;
- (MinScrollMenuItem *)scrollMenu:(MinScrollMenu*)menu itemAtIndex:(NSInteger)index;
- (void)scrollMenu:(MinScrollMenu*)menu didSelectedItem: (MinScrollMenuItem *)item atIndex: (NSInteger)index;

Imitate the four proxy methods introduced earlier.

2 Layout

Create a subclass inheriting UIView named MinScrollMenu.
(1) Add a scrollView property and initialize it on MinScrollMenu, the frame size is the same as the parent view. Just like the UITableView of the system, we also use scrollView to implement functions.

@property (nonatomic, strong) UIScrollView *scrollView;/*!< Scroll View with Horizontal Scroll */

(2) Add an attribute inherited from UIView, named contentView, and initially add it to the scrollView created previously. Frame can be set up first. This view is mainly used to load the cells to be displayed in the future. The size of frame needs to be calculated later.

@property (nonatomic, strong) UIView *contentView;/*!< view loading item */

(3) The following attributes are mainly used to cache data from cell data sources

@property (nonatomic, strong) NSMutableArray *visibleItems;/*!< item Array in Screen Range */
@property (nonatomic, strong) NSMutableSet *reuseableItems;/*!< Reuse pool */
@property (nonatomic, strong) NSMutableDictionary *infoDict;/*!< Cache item selected information */
@property (nonatomic, strong) NSMutableDictionary *frameDict;/*!< Cache the frame of item */

3 Processing Data Source Data

(1) Get the number of item s according to the agent

    if (self.delegate != nil && [self.delegate respondsToSelector:@selector(numberOfMenuCount:)]) {
        _count = [self.delegate numberOfMenuCount:self];
    }

(2) Cyclically create the cell item, because it scrolls horizontally, so it mainly gets the width and changes the value of the x-axis to calculate the frame. Calculate the frames of all items and cache them in the dictionary. Then you can get the frames of contentView that were not set up before, and post the code:

    for (NSInteger i = 0; i < _count; ++i) {
        //Get the width of item
        width = [self itemWidthWithIndex:i];
        CGRect itemFrame = CGRectMake(x, y, width, height);

        // Not added to the visibleItems array beyond the display range of the screen
        CGFloat maxX = CGRectGetMaxX(itemFrame);
        CGFloat overItemWidth = width*3;
        if (i < _count-3) {
            overItemWidth = width + [self itemWidthWithIndex:i+1] + [self itemWidthWithIndex:i+2];
        }
        isOverScreenWidth = maxX > ScreenWidth + overItemWidth;
        if (!isOverScreenWidth) {
            // Get the item, set the Frame, and add it to the contentView
            MinScrollMenuItem *item = [self itemWithIndex:i];
            if (item) {
                item.frame = itemFrame;
                [_contentView addSubview:item];

                // Add click gestures
                UITapGestureRecognizer *tapGst = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapItem:)];
                [item addGestureRecognizer:tapGst];

                item.tag = ITEMTAG + i;

                // Add to the visibleItems array
                [_visibleItems addObject:item];
            }
        }

        // Caching data
        [_frameDict setObject:@(i) forKey:NSStringFromCGRect(itemFrame)];
        [_infoDict setObject:@(NO) forKey:@(i)];

        // Computing contentSize of scrollView
        scrollContentWidth = maxX;

        x += width;
    }

    _scrollView.contentSize = CGSizeMake(scrollContentWidth, height);
    _contentView.frame = CGRectMake(0, 0, scrollContentWidth, height);

Complete the above code and run it. You can see that item is displayed, but the scroll processing is not complete, so the right area of scrollView is still blank. Next is the implementation of the core scroll processing and reuse mechanism.
(3) Reuse and rolling
Reuse and scrolling are done simultaneously. When the tableView scrolls to the right, if the leftmost item has left the screen range, it can be stored in the reuse pool. At the same time, according to the item identifier, the item is removed from the reuse pool, and the frame is set to be added to the visibleItems array. So you can recycle several items to show the contents of n items.
Implementation of UIScrollView Agent Method

- (void)scrollViewDidScroll:(UIScrollView *)scrollView

This method allows you to obtain the displacement content Offset of the current scrollView movement
The specific ideas are as follows:

Scroll. png

Reuse mechanism code:
Internally find reusable item s:

    NSSet *tempSet = [_reuseableItems filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"reuseIdentifer == %@", reuseItem.reuseIdentifer]];
    // Query for item s with the same multiplexer in the multiplexing pool
    if (![tempSet isSubsetOfSet:_reuseableItems] || tempSet.count == 0) {
        // If not, add item to the reuse pool
        [_reuseableItems addObject:reuseItem];
    }

Expose the code for the API implementation:

- (MinScrollMenuItem *)dequeueItemWithIdentifer:(NSString *)identifer {
    NSSet *tempSet = [_reuseableItems filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"reuseIdentifer == %@", identifer]];
    MinScrollMenuItem *item = tempSet.anyObject;
    return item;
}

You can also use circular lookups without predicate queries, because reuse pools typically require only one identifier item. So the number of Set elements is relatively small.
(4) Implementation of data refresh reloadData method
The idea is shown in the following figure.

Data refresh.png

(5) Click on item callback response method to achieve:
Implementation in Menu:
First, the dictionary that saves the selected state before traversing is changed to NO if value is YES.
Second, traverse the screen to display the item array visibleItems, setting the item's isSelected attribute to NO
Thirdly, the selected item state is changed to select, and index is obtained by tag value, which is saved in the cache dictionary.
Fourth, callback agent method, notification controller
Paste specific code:

- (void)tapItem: (UITapGestureRecognizer *)tapGst {
    [_infoDict enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, NSNumber *obj, BOOL * _Nonnull stop) {
        *stop = obj.boolValue;
        if (*stop) {
            _infoDict[key] = @(NO);
        }
    }];
    for (MinScrollMenuItem *item in _visibleItems) {
        item.isSelected = NO;
        [_infoDict setObject:@(NO) forKey:@(item.tag-ITEMTAG)];
    }

    if ([tapGst.view isKindOfClass:[UIView class]]) {

        UIView *tempView = tapGst.view;
        MinScrollMenuItem *item = (MinScrollMenuItem *)tempView;

        if ([item isKindOfClass:[MinScrollMenuItem class]]) {
            item.isSelected = YES;
            [_infoDict setObject:@(YES) forKey:@(item.tag-ITEMTAG)];
            if (self.delegate && [self.delegate respondsToSelector:@selector(scrollMenu:didSelectedItem:atIndex:)]) {
                [self.delegate scrollMenu:self didSelectedItem:item atIndex:item.tag - ITEMTAG];
            }
        }
    }
}

Implementation in Item:
First, item adds a selected CALayer class as an attribute to create a layer added to the item. Don't forget to set it to hide, hidden=YES. Provide an open BOOL value isSelected attribute.
Second, rewrite the isSelected attribute set method, and when selected, modify the hidden of layer to NO.

Postscript: The four basic proxy methods of tableview have been implemented. Through this horizontal scrolling tableview-like control, we have a deeper understanding of the working principle of tableview. Of course, tableview has many functions that have not been implemented, but the basic framework is completed, and some functional things can be added later. You can use github address: MinScrollMenu Download the source code to see, do not abandon the dot star bar:)

Keywords: Attribute github

Added by CJLeah on Tue, 02 Jul 2019 23:43:41 +0300