iOS tableView类似携程/美团城市筛选,自定义sectionIndex

很多app中都需要城市选择,最近自己写了一个城市筛选、搜索功能,以及tableView双表联动,上拉自动至下一个分类,tableView的折叠布局。

Demo -- 传送门

一、双表联动

1、上拉至下一个分类

1.gif
  • 给rightTableView添加footRefresh 在刷新回调的时候进行操作,给leftTableView选中至当前的下一个indexPathRow 并同时将下一个rightTableView分类的dataSource传送过去,并刷新leftTableView、rightTableView两个表。将leftTableView当前选中的indexPath滚动到顶部。
#pragma mark - rightTableView刷新回调
- (void)childTableCallback {
    LBWeakSelf(self);
    self.childTableView.freshFinishCallback = ^ {
        LBStrongSelf(self);
        [self setCurrentIndexWithRow:self.currentIndex.row+1];
    };
}
- (void)setCurrentIndexWithRow:(NSInteger)row {
    NSIndexPath *currentIndex = [NSIndexPath indexPathForRow:row inSection:0];
    // 调用leftTableView的didSelectRowAtIndexPath方法实现选中下一个indexPathRow
    [self.baseTableView tableView:self.baseTableView.baseTableView didSelectRowAtIndexPath:currentIndex];
    // 记录当前选中的indexPath
    self.currentIndex = currentIndex;
    [self.baseTableView setValue:self.currentIndex forKey:@"selectedIndex"];
    [self.baseTableView.baseTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:row inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
  • 点击leftTableView 将当前的分类数据传送给rightTableView并刷新rightTableView。
#pragma mark - leftTableView点击回调
- (void)baseTableViewCallback {
    LBWeakSelf(self);
    self.baseTableView.didSelectCellCallback = ^(NSIndexPath *indexPath, UITableViewCell *currentBaseCell) {
        LBStrongSelf(self);
        LBModel* model = self.dataSource[indexPath.row];
        // 数据源传给rightTableView
        [self.childTableView setValue:model.cityList forKey:@"childData"];
        // 刷新rightTableView
        [self.childTableView reload];
        self.currentIndex = indexPath;
    };
}

2、所有区联动

2.gif
  • 监听rightTableView滚动,并获取当前屏幕第一个section回调给leftTableView进行刷表同时将当前的section滚动到顶部
#pragma mark - rightTableView滚动监听回调
- (void)scrollwithIndex:(NSIndexPath *)index {
    self.selectedIndex = index;
    //tableview滚动到指定的行:
    [self.baseTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:index.row inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
    [self.baseTableView reloadData];
}

二、tableView折叠

3.gif
  • 定义BOOL值isFlod 记录是否可以展开,定义currentIndex记录当前展开的index
// 是否可以展开
@property(nonatomic, assign)BOOL isFlod;
// 当前展开的index
@property(nonatomic, assign)NSInteger currentIndex;
  • 给tableViewSection添加UIButton并设置tag为当前section
    sectionBtn.tag = section;
    sectionBtn.selected = self.currentIndex==section&&self.isFlod==YES?YES:NO;
  • Button点击方法设置isFlod值和currentIndex值并刷新tableView
- (void)sectionSelect:(UIButton*)sender {
    self.currentIndex = sender.tag;
    sender.selected = !sender.selected;
    self.isFlod = sender.selected;
    [self.foldTableView reloadData];
}
  • 在tableView数据源方法中,判断当前的section是否为展开状态并给出当前section有多少row
- (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;
}

三、城市筛选

4.gif
  • 前面2个section分别是历史的筛选和热门筛选tableViewCell嵌套的collectionView,后面是按照首字母A~Z的分区城市的tableViewCell,这个不多说了,由于我的地址数据是按照省市区分的,所以自己又重新排了一次数据结构。

汉字转拼音

//获取拼音首字母(传入汉字字符串, 返回大写拼音首字母)
+(NSString *)FirstCharactor:(NSString *)pString {
    //转成了可变字符串
    NSMutableString *pStr = [NSMutableString stringWithString:pString];
    //先转换为带声调的拼音
    CFStringTransform((CFMutableStringRef)pStr,NULL, kCFStringTransformMandarinLatin,NO);
    //再转换为不带声调的拼音
    CFStringTransform((CFMutableStringRef)pStr,NULL, kCFStringTransformStripDiacritics,NO);
    //多音字处理
    if ([[(NSString *)pString substringToIndex:1] compare:@"长"] == NSOrderedSame) {
        [pStr replaceCharactersInRange:NSMakeRange(0, 5) withString:@"chang"];
    }
    if ([[(NSString *)pString substringToIndex:1] compare:@"沈"] == NSOrderedSame) {
        [pStr replaceCharactersInRange:NSMakeRange(0, 4) withString:@"shen"];
    }
    if ([[(NSString *)pString substringToIndex:1] compare:@"厦"] == NSOrderedSame) {
        [pStr replaceCharactersInRange:NSMakeRange(0, 3) withString:@"xia"];
    }
    if ([[(NSString *)pString substringToIndex:1] compare:@"地"] == NSOrderedSame) {
        [pStr replaceCharactersInRange:NSMakeRange(0, 3) withString:@"di"];
    }
    if ([[(NSString *)pString substringToIndex:1] compare:@"重"] == NSOrderedSame) {
        [pStr replaceCharactersInRange:NSMakeRange(0, 5) withString:@"chong"];
    }
    //转化为大写拼音    
    NSString *pPinYin = [pStr uppercaseString];  // lowercaseString 转小写
    /*如果要返回所有的汉字转拼音,直接return pPinYin就可以了,转完的是中间带有空格的拼音,下面是去掉中间空格的方法*/
    /**去空格
    NSString *ciytString = [NSString stringWithFormat:@"%@", pPinYin];
    NSString *cityName = [ciytString stringByReplacingOccurrencesOfString:@" "     withString:@""]; */
    //获取并返回首字母
    return [pPinYin substringToIndex:1];
}

判断字符串中是否包含有汉字

// 是否包含汉字
- (BOOL)includeChinese {
    for(int i=0; i< [self length];i++) {
        int a =[self characterAtIndex:i];
        if( a >0x4e00&& a <0x9fff){
            return YES;
        }
    }
    return NO;
}
  • 最上面2个section的cell点击回调
- (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) {
        self.collectionCallback(self.dataArray[indexPath.item],indexPath.item,self.index);
    }
}
#pragma mark - 历史和热门cell点击的回调--historyAndHotCellCallback
- (void)historyAndHotCellCallback:(LBHistoryCell *)cell {
    LBWeakSelf(self);
    cell.collectionCallback = ^(NSString *selectText,NSInteger collectionSelectIndex,NSIndexPath *index) {
        LBStrongSelf(self);
        if (index.section==1) { // 热门回调,存入缓存
            [LBUserDefaultTool saveHotSelectedData:collectionSelectIndex];
        } else {                // 历史回调
            NSMutableDictionary* dict = self.dataSource[1];
            NSMutableArray* cellArr = dict[@"cityName"];
            // 清热门选中缓存,重新存
            [LBUserDefaultTool removeHotSelected];
            // 查找热门中的数据与历史里选中的数据相同
            [cellArr enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
                if ([object isEqualToString:selectText]) {
                    [LBUserDefaultTool saveHotSelectedData:idx];
                }
            }];
        }
        if (self.selectCallback) {
            self.selectCallback(selectText);
        }
    };
}
  • 下面tableViewCell点击回调
#pragma mark - tableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:NO];
    // 下面tableView点击
    if (indexPath.section!=0&&indexPath.section!=1) {
        [LBUserDefaultTool removeHotSelected];
        NSMutableDictionary* dict = self.dataSource[indexPath.section];
        NSString* selectText = dict[@"cityName"][indexPath.row];
        if (self.selectCallback) {
            self.selectCallback(selectText);
        }
    }
    // cell选中的标记符变化和titleLabel颜色变化
    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);
}

四、城市搜索

搜索.gif
  • UITextField实时监听input输入,input的length大于1,去遍历所有数据查找,利用上面汉字转拼音并去空格的方法,与input的数据作对比,如果城市的拼音数据包含了input的数据,拼接成model并存入searchArray数组中作为searchTableView的dataSource展示。
// 搜索input实时搜索
- (void)setSearchWithInputText:(NSString *)inputText {
    if (inputText.length > 0) {     // 这里判断length要大于0,是因为汉字转拼音的方法不能传空值
        [self.searchArray removeAllObjects];
        // length大于1,进行数据对比
        if ([NSString transform:inputText].length > 1) {
            // 循环所有数据
            [self.dataSource enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
                NSDictionary* dict = object;
                // firstText A~Z字母
                NSString* firstText = dict[@"cityId"];
                // 输入的(汉字转拼音)首字母是否属于A~Z之间 并拼接数据存入搜索数组searchArray中
                if ([[NSString FirstCharactor:inputText] isEqualToString:firstText]) {
                    NSMutableDictionary* tempDict = @{}.mutableCopy;
                    NSMutableArray* tempArr = @[].mutableCopy;
                    tempDict[@"cityId"] = firstText;
                    // 遍历所有城市的数组
                    [dict[@"cityName"] enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
                        NSString* cityName = object;
                        // 所有城市的拼音包含输入的拼音,就是符合的数据,存入搜索的数组中
                        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;
    }
}

五、自定义indexView

5.gif
6.gif
  • 定义一个indexView,实现dataSource和delegate方法
@protocol LBIndexViewDataSource <NSObject>
// 返回一共多少个section
- (NSInteger)numberOfItemViewForSectionIndexView:(LBIndexView *)sectionIndexView;
// 返回每个section的title
- (NSString *)sectionIndexView:(LBIndexView *)sectionIndexView
               titleForSection:(NSInteger)section;
@end

@protocol LBIndexViewDelegate <NSObject>
// 点击IndexItemView事件
- (void)sectionIndexView:(LBIndexView *)sectionIndexView
        didSelectSection:(NSInteger)section;
@end

注意:避免delegate方法里section里的row为空,滚动到顶部crash,可以进行section删除,我这里没操作(小伙伴们可以自行操作)

#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;
        }
    }
    // 避免section里面的row为空,滚动到顶部crash
    [self.cityTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
  • 下面是indexView的几个属性配置
// 是否显示选中提示图,默认是YES
@property(nonatomic, assign)BOOL isShowCallout;
// 选中提示图的样式
@property(nonatomic, assign)NSInteger calloutViewType;
// 选中背景的样式
@property(nonatomic, assign)NSInteger titleBGViewType;
// 提示图的主题色
@property(nonatomic, strong)UIColor* schemeColor;
  • 实现reloadIndeView方法
// 创建数据源
- (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) {
            make.centerX.offset(0);
            make.top.offset(offsetY);
            make.width.mas_equalTo(idx==0||idx==1?LBFit(30):self.itemViewHeight);
            make.height.mas_equalTo(self.itemViewHeight);
        }];
        offsetY += self.itemViewHeight;
    }];
}

分别实现4个touch方法,也是indexView的核心

  • touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    self.bgView.hidden = NO;
    UITouch *touch = [touches anyObject];
    // 获取到touch的point
    CGPoint touchPoint = [touch locationInView:self];
    // 遍历indexView的数据源,做点击操作和highlight操作
    for (LBIndexItemView *itemView in self.itemViewList) {
        if (CGRectContainsPoint(itemView.frame, touchPoint)) {
            [self selectItemViewForSection:itemView.section];
            self.highlightedItemIndex = itemView.section;
            return;
        }
    }
    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;
                return;
            }
        }
    }
}
  • touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    self.bgView.hidden = YES;
    // 取消所有higlight的item
    [self unhighlightAllItems];
    self.highlightedItemIndex = -1;
}
  • touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [self touchesCancelled:touches withEvent:event];
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容