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:)