IOS tableview is similar to Ctrip / meituan City screening, with customized sectionindex


Many apps need city selection. Recently, I wrote a city filtering and search function, as well as the double table linkage of tableview, which is automatically pulled up to the next category and the folding layout of tableview.

Demo — Portal

1、 Double meter linkage

1. Pull up to the next category

IOS tableview is similar to Ctrip / meituan City screening, with customized sectionindex

  • Add footrefresh to the righttableview and operate when refreshing the callback. Select the next indexpathrow for the lefttableview and transfer the datasource classified by the next righttableview at the same time, and refresh the lefttableview and righttableview tables. Scroll the currently selected indexpath of lefttableview to the top.
#Pragma mark - righttableview refresh callback
- (void)childTableCallback {
    self.childTableView.freshFinishCallback = ^ {
        [self setCurrentIndexWithRow:self.currentIndex.row+1];
- (void)setCurrentIndexWithRow:(NSInteger)row {
    NSIndexPath *currentIndex = [NSIndexPath indexPathForRow:row inSection:0];
    //Call the didselectrowatindexpath method of lefttableview to select the next indexpathrow
    [self.baseTableView tableView:self.baseTableView.baseTableView didSelectRowAtIndexPath:currentIndex];
    //Record the currently selected indexpath
    self.currentIndex = currentIndex;
    [self.baseTableView setValue:self.currentIndex forKey:@"selectedIndex"];
    [self.baseTableView.baseTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:row inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
  • Click lefttableview to transfer the current classification data to righttableview and refresh righttableview.
#Pragma mark - lefttableview click callback
- (void)baseTableViewCallback {
    self.baseTableView.didSelectCellCallback = ^(NSIndexPath *indexPath, UITableViewCell *currentBaseCell) {
        LBModel* model = self.dataSource[indexPath.row];
        //Data source passed to righttableview
        [self.childTableView setValue:model.cityList forKey:@"childData"];
        //Refresh righttableview
        [self.childTableView reload];
        self.currentIndex = indexPath;

2. All zone linkage

IOS tableview is similar to Ctrip / meituan City screening, with customized sectionindex

  • Listen to the scrolling of righttableview, get the first section callback of the current screen, brush the table to lefttableview, and scroll the current section to the top at the same time
#Pragma mark - righttableview scrolling listening callback
- (void)scrollwithIndex:(NSIndexPath *)index {
    self.selectedIndex = index;
    //Tableview scrolls to the specified row:
    [self.baseTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:index.row inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
    [self.baseTableView reloadData];

2、 Tableview collapse

IOS tableview is similar to Ctrip / meituan City screening, with customized sectionindex

  • Defines whether the bool value isflod record can be expanded, and defines the index of the currentindex record currently expanded
//Can I expand
@property(nonatomic, assign)BOOL isFlod;
//Currently expanded index
@property(nonatomic, assign)NSInteger currentIndex;
  • Add a uibutton to the tableviewsection and set the tag to the current section
    sectionBtn.tag = section;
    sectionBtn.selected = self.currentIndex==section&&self.isFlod==YES?YES:NO;
  • Button click method to set isflod value and currentindex value and refresh tableview
- (void)sectionSelect:(UIButton*)sender {
    self.currentIndex = sender.tag;
    sender.selected = !sender.selected;
    self.isFlod = sender.selected;
    [self.foldTableView reloadData];
  • In the tableview data source method, judge whether the current section is expanded and give the number of rows in the current section
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if (self.currentIndex == section) {
        LBModel* childModel = self.dataSource[section];
        return self.isFlod==YES?childModel.cityList.count:0;
    return 0;

3、 City screening

IOS tableview is similar to Ctrip / meituan City screening, with customized sectionindex

  • The first two sections are the nested collectionview of the historical filter and the popular filter tableviewcell, followed by the tableviewcell of the divided cities according to the initials a ~ Z. I won’t say much. Because my address data is distinguished by provinces and cities, I rearranged the data structure again.

Chinese character to Pinyin

//Get Pinyin initial (pass in Chinese character string and return uppercase Pinyin initial)
+(NSString *)FirstCharactor:(NSString *)pString {
    //Converted to a variable string
    NSMutableString *pStr = [NSMutableString stringWithString:pString];
    //First convert to Pinyin with tone
    CFStringTransform((CFMutableStringRef)pStr,NULL, kCFStringTransformMandarinLatin,NO);
    //Then convert to Pinyin without tone
    CFStringTransform((CFMutableStringRef)pStr,NULL, kCFStringTransformStripDiacritics,NO);
    //Polyphonic word processing
    If ([[(nsstring *) pstring substringtoindex: 1] Compare: @ "long"] = = nsordedsame){
        [pStr replaceCharactersInRange:NSMakeRange(0, 5) withString:@"chang"];
    If ([[(nsstring *) pstring substringtoindex: 1] Compare: @ "Shen"] = = nsordedsame){
        [pStr replaceCharactersInRange:NSMakeRange(0, 4) withString:@"shen"];
    If ([[(nsstring *) pstring substringtoindex: 1] Compare: @ "Xia"] = = nsordedsame){
        [pStr replaceCharactersInRange:NSMakeRange(0, 3) withString:@"xia"];
    If ([[(nsstring *) pstring substringtoindex: 1] Compare: @ "ground"] = = nsordedsame){
        [pStr replaceCharactersInRange:NSMakeRange(0, 3) withString:@"di"];
    If ([[(nsstring *) pstring substringtoindex: 1] Compare: @ "heavy"] = = nsordedsame){
        [pStr replaceCharactersInRange:NSMakeRange(0, 5) withString:@"chong"];
    //Convert to uppercase Pinyin    
    NSString *pPinYin = [pStr uppercaseString];  //  Lowercasestring to lowercase
    /*If you want to return all Chinese characters to Pinyin, just return ppinyin directly. The Pinyin with a space in the middle is finished. The following is the method to remove the space in the middle*/
    /**Go to space
    NSString *ciytString = [NSString stringWithFormat:@"%@", pPinYin];
    NSString *cityName = [ciytString stringByReplacingOccurrencesOfString:@" "     withString:@""]; */
    //Gets and returns the initial
    return [pPinYin substringToIndex:1];

Judge whether the string contains Chinese characters

//Whether to include Chinese characters
- (BOOL)includeChinese {
    for(int i=0; i< [self length];i++) {
        int a =[self characterAtIndex:i];
        if( a >0x4e00&& a <0x9fff){
            return YES;
    return NO;
  • Click the cell callback of the top two sections
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
    LBCollectionCell * cell = (LBCollectionCell *)[collectionView cellForItemAtIndexPath:indexPath];
    LBCollectionCell * currentCell;
    if (self.index.section==0&&indexPath.row!=0) {
        currentCell = (LBCollectionCell *)[collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
    if (self.index.section==1&&indexPath.row!=[self.selectedHotIndex integerValue]) {
        currentCell = (LBCollectionCell *)[collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForRow:[self.selectedHotIndex integerValue] inSection:0]];
    cell.title.backgroundColor = LBUIColorWithRGB(0x3CB371, .5);
    cell.title.textColor = LBUIColorWithRGB(0x228B22, 1);
    currentCell.title.backgroundColor = LBUIColorWithRGB(0xF5F5F5, 1);
    currentCell.title.textColor = LBUIColorWithRGB(0x130202, 1);
    if (self.collectionCallback) {
#Pragma mark - callback of history and popular cell clicks -- historyandhotcellcallback
- (void)historyAndHotCellCallback:(LBHistoryCell *)cell {
    cell.collectionCallback = ^(NSString *selectText,NSInteger collectionSelectIndex,NSIndexPath *index) {
        If (index. Section = = 1) {// popular callback, stored in cache
            [LBUserDefaultTool saveHotSelectedData:collectionSelectIndex];
        }Else {// history callback
            NSMutableDictionary* dict = self.dataSource[1];
            NSMutableArray* cellArr = dict[@"cityName"];
            //Clear the selected cache and save again
            [LBUserDefaultTool removeHotSelected];
            //The data in the popular search is the same as the data selected in the history
            [cellArr enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
                if ([object isEqualToString:selectText]) {
                    [LBUserDefaultTool saveHotSelectedData:idx];
        if (self.selectCallback) {
  • Click the callback of tableviewcell below
#pragma mark - tableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:NO];
    //Click tableview below
    if (indexPath.section!=0&&indexPath.section!=1) {
        [LBUserDefaultTool removeHotSelected];
        NSMutableDictionary* dict = self.dataSource[indexPath.section];
        NSString* selectText = dict[@"cityName"][indexPath.row];
        if (self.selectCallback) {
    //Changes in the color of the selected cell and label
    LBCityCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    LBCityCell *currentCell = [tableView cellForRowAtIndexPath:self.currentSelectedIndex];
    currentCell.cityLabel.textColor = LBUIColorWithRGB(0x130202, 1);
    currentCell.selectedImg.alpha = 0;
    cell.selectedImg.alpha = 1;
    cell.cityLabel.textColor = LBUIColorWithRGB(0x228B22, 1);

4、 City Search

IOS tableview is similar to Ctrip / meituan City screening, with customized sectionindex

Search gif
  • Uitextfield monitors the input in real time. The length of input is greater than 1. Go through all data search. Use the above method of converting Chinese characters to Pinyin and removing spaces to compare with the input data. If the Pinyin data of the city contains the input data, splice it into a model and store it in the searcharray array as the datasource of searchtableview.
//Search input real time search
- (void)setSearchWithInputText:(NSString *)inputText {
    If (inputtext. Length > 0) {// it is judged here that the length should be greater than 0 because the method of converting Chinese characters to Pinyin cannot pass a null value
        [self.searchArray removeAllObjects];
        //If the length is greater than 1, compare the data
        if ([NSString transform:inputText].length > 1) {
            //Cycle all data
            [self.dataSource enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
                NSDictionary* dict = object;
                //Firsttext a ~ Z letters
                NSString* firstText = dict[@"cityId"];
                //Whether the first letter of the input (Chinese character to Pinyin) belongs to a ~ Z, and the splicing data is stored in the search array searcharray
                if ([[NSString FirstCharactor:inputText] isEqualToString:firstText]) {
                    NSMutableDictionary* tempDict = @{}.mutableCopy;
                    NSMutableArray* tempArr = @[].mutableCopy;
                    tempDict[@"cityId"] = firstText;
                    //Traverses an array of all cities
                    [dict[@"cityName"] enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
                        NSString* cityName = object;
                        //The Pinyin of all cities includes the input Pinyin, that is, the matching data, which is stored in the search array
                        if ([[NSString transform:cityName] containsString:[NSString transform:inputText]]) {
                            [tempArr addObject:cityName];
                    tempDict[@"cityName"] = tempArr;
                    [self.searchArray addObject:tempDict];
        self.searchTableView.alpha = [NSString transform:inputText].length>1?1:0;
        [self.searchTableView setValue:self.searchArray forKey:@"dataSource"];
    } else {
        self.searchTableView.alpha = 0;

5、 Custom indexview

IOS tableview is similar to Ctrip / meituan City screening, with customized sectionindex

IOS tableview is similar to Ctrip / meituan City screening, with customized sectionindex

  • Define an indexview to implement datasource and delegate methods
@protocol LBIndexViewDataSource <NSObject>
//How many sections are returned
- (NSInteger)numberOfItemViewForSectionIndexView:(LBIndexView *)sectionIndexView;
//Return the title of each section
- (NSString *)sectionIndexView:(LBIndexView *)sectionIndexView

@protocol LBIndexViewDelegate <NSObject>
//Click indexitemview event
- (void)sectionIndexView:(LBIndexView *)sectionIndexView

Note: avoid that the row in the section in the delegate method is empty. Scroll to the top crash to delete the section. I don’t have any operation here (partners can operate by themselves)

#pragma mark - LBIndexViewDataSource
- (NSInteger)numberOfItemViewForSectionIndexView:(LBIndexView *)sectionIndexView {
    return self.cityTableView.numberOfSections;

- (NSString *)sectionIndexView:(LBIndexView *)sectionIndexView titleForSection:(NSInteger)section {
    NSMutableArray* sectionArr = @[].mutableCopy;
    [self.dataSource enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
        NSMutableDictionary* dict = object;
        [sectionArr addObject:dict[@"cityId"]];
    return sectionArr[section];
#pragma mark - LBIndexViewDelegate
- (void)sectionIndexView:(LBIndexView *)sectionIndexView didSelectSection:(NSInteger)section {
    NSMutableDictionary* dict = self.dataSource[section];
    NSMutableDictionary* dictionary = self.dataSource.count<=section?self.dataSource[section+1]:@{}.mutableCopy;
    NSMutableArray* arr = [NSMutableArray arrayWithArray:dict[@"cityName"]];
    NSMutableArray* array = [NSMutableArray arrayWithArray:dictionary[@"cityName"]];
    if (arr.count <= 0) {
        section = section+1;
        if (array.count <= 0) {
            section = section+1;
    //Avoid empty row in the section and scroll to the top crash
    [self.cityTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section] atScrollPosition:UITableViewScrollPositionTop animated:YES];
  • The following are several property configurations of indexview
//Whether to display the selected prompt chart. The default is yes
@property(nonatomic, assign)BOOL isShowCallout;
//Check the style of the prompt chart
@property(nonatomic, assign)NSInteger calloutViewType;
//Select the style of the background
@property(nonatomic, assign)NSInteger titleBGViewType;
//Theme color of prompt diagram
@property(nonatomic, strong)UIColor* schemeColor;
  • Implement the reloadindeview method
//Create data source
- (void)reloadIndexView {
    NSInteger numberOfItems = 0;
    if (_dataSource && [_dataSource respondsToSelector:@selector(numberOfItemViewForSectionIndexView:)]) {
        numberOfItems = [_dataSource numberOfItemViewForSectionIndexView:self];
    for (int i = 0; i < numberOfItems; i++) {
        if (_dataSource && [_dataSource respondsToSelector:@selector(sectionIndexView:titleForSection:)]) {
            LBIndexItemView* itemView = [LBIndexItemView new];
            itemView.section = i;
            itemView.titleLabel.text = [_dataSource sectionIndexView:self titleForSection:i];
            itemView.schemeColor = self.schemeColor?self.schemeColor:LBUIColorWithRGB(0x228B22, 1);
            [self.itemViewList addObject:itemView];
            [self addSubview:itemView];
    [self layoutItemViews];
- (void)layoutItemViews {
    if (self.itemViewList.count) {
        self.itemViewHeight = LBScreenH/3*2/(CGFloat)(self.itemViewList.count);
    __block CGFloat offsetY = 0.f;
    [self.itemViewList enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
        LBIndexItemView *itemView = object;
        [itemView setHighlighted:NO animated:NO section:idx type:self.titleBGViewType];
        [itemView mas_makeConstraints:^(MASConstraintMaker *make) {
        offsetY += self.itemViewHeight;

Implement four touch methods respectively, which is also the core of indexview

  • touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    self.bgView.hidden = NO;
    UITouch *touch = [touches anyObject];
    //Get the point of the touch
    CGPoint touchPoint = [touch locationInView:self];
    //Traverse the data source of indexview and do click and highlight operations
    for (LBIndexItemView *itemView in self.itemViewList) {
        if (CGRectContainsPoint(itemView.frame, touchPoint)) {
            [self selectItemViewForSection:itemView.section];
            self.highlightedItemIndex = itemView.section;
    self.highlightedItemIndex = -1;
  • touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    self.bgView.hidden = NO;
    UITouch *touch = [touches anyObject];
    CGPoint touchPoint = [touch locationInView:self];
    for (LBIndexItemView *itemView in self.itemViewList) {
        if (CGRectContainsPoint(itemView.frame, touchPoint)) {
            if (itemView.section != self.highlightedItemIndex) {
                [self selectItemViewForSection:itemView.section];
                self.highlightedItemIndex = itemView.section;
  • touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    self.bgView.hidden = YES;
    //Cancel all highlight items
    [self unhighlightAllItems];
    self.highlightedItemIndex = -1;
  • touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [self touchesCancelled:touches withEvent:event];