很多app中都需要城市选择,最近自己写了一个城市筛选、搜索功能,以及tableView双表联动,上拉自动至下一个分类,tableView的折叠布局。
Demo -- 传送门
一、双表联动
1、上拉至下一个分类
- 给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、所有区联动
- 监听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折叠
- 定义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;
}
三、城市筛选
- 前面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);
}
四、城市搜索
- 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
- 定义一个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];
}