实现效果
实现思路
初始化字母表数组
NSArray *charArr = @[@"A",@"B",@"C",@"D",@"E",@"F",@"G",@"H",@"I",@"J",@"K",@"L",@"M",@"N",@"O",@"P",@"Q",@"R",@"S",@"T",@"U",@"V",@"W",@"X",@"Y",@"Z",@"#"];
_letterDataArray = [[NSMutableArray alloc] init];
[charArr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
LetterWordItemModel *item = [[LetterWordItemModel alloc] init];
item.content = obj;
[_letterDataArray addObject:item];
}];
把字符串转成LetterWordItemModel,增加一个是否选中的属性,方便于后续控制字母表的显示样式。
@interface LetterWordItemModel : NSObject
@property (nonatomic,strong) NSString *content;
@property (nonatomic,assign) BOOL isCheck;//是否被选中
@end
初始化内容的数据源数组
@property (nonatomic,strong) NSArray<NSArray<LetterTableItemModel *> *> *dataArray;//内容数组
@interface LetterTableItemModel : NSObject
@property (nonatomic,strong) NSString *content;
@property (nonatomic,strong) NSString *firstChar;
@end
tableView是一个分组的列表,需要的数据源为数组内层嵌套一个数组的形式。
LetterTableItemModel其中这个类至少需要如上两个属性。
content用于显示内容,firstChar用于存储字符串的第一个字的第一个拼音字母,根据此字段来为数组分组。
可以根据自身需求灵活给该类增加字段,比如电话号码等等属性。
NSMutableString *pinyin = [[NSMutableString alloc] initWithString:obj.content];
CFStringTransform((__bridge CFMutableStringRef)pinyin, NULL, kCFStringTransformMandarinLatin, NO);
CFStringTransform((__bridge CFMutableStringRef)pinyin, NULL, kCFStringTransformStripDiacritics, NO);
obj.firstChar = [[pinyin substringToIndex:1] uppercaseString];
用CFStringTransform方法来获取字符串的第一个拼音字母,并且统一转换成大写。
初始化tableView。
_tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.frame), CGRectGetHeight(self.frame))];//确定内容列表的位置和大小
[self addSubview:_tableView];
_tableView.dataSource = self; _tableView.delegate = self;
[_tableView registerNib:[UINib nibWithNibName:@"ContentTableViewCell" bundle:nil] forCellReuseIdentifier:contentCellIdentifier];
初始化右边竖向字母tableView。
_letterTableView = [[UITableView alloc] initWithFrame:CGRectMake([[UIScreen mainScreen] bounds].size.width - letterTableViewWidth, [[UIScreen mainScreen] bounds].size.height/2 - letterTableViewCellHeight * _letterDataArray.count/2, letterTableViewWidth, letterTableViewCellHeight * _letterDataArray.count)];//确定字母表的位置和大小
[self addSubview:_letterTableView];
_letterTableView.dataSource = self;
_letterTableView.delegate = self;
[_letterTableView registerNib:[UINib nibWithNibName:@"LetterTableViewCell" bundle:nil] forCellReuseIdentifier:letterCellIdentifier];
_letterTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
_letterTableView.backgroundColor = [UIColor clearColor];
_letterTableView.bounces = NO;
给字母右边的字母tableView添加一个滑动手势。
//给字母表添加拖动手势
UIPanGestureRecognizer *letterPan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(letterPan:)];
[_letterTableView addGestureRecognizer:letterPan];
分别实现两个tableView的DataSource。
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
if (tableView == _tableView) {
return _dataArray.count;
}else {
return 1;
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (tableView == _tableView) {
return ((NSArray *)_dataArray[section]).count;
}else {
return _letterDataArray.count;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (tableView == _tableView) {
ContentTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:contentCellIdentifier];
cell.contentLabel.text = _dataArray[indexPath.section][indexPath.row].content;
return cell;
}else {
LetterTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:letterCellIdentifier];
cell.letterWordLabel.text = _letterDataArray[indexPath.row].content;
//配置字母表字母选中样式
if (_letterDataArray[indexPath.row].isCheck) {
cell.letterWordLabel.textColor = [UIColor whiteColor];
cell.letterWordLabel.backgroundColor = [UIColor colorWithRed:0x56/255.0f green:0x70/255.0f blue:0xfe/255.0f alpha:1.0f];
}else {
cell.letterWordLabel.textColor = [UIColor colorWithRed:0xa2/255.0f green:0xa7/255.0f blue:0xc7/255.0f alpha:1.0f];
cell.letterWordLabel.backgroundColor = [UIColor clearColor];
}
return cell;
}
}
分别实现两个tableView的delegate。
包括配置cell高度,sectionheaderView的高度和样式。
两个tableView的点击事件。
#pragma mark - UITableViewDelegate
//配置两个tableView的cell高度
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (tableView == _tableView) {
return contentTableViewCellHeight;
}else {
return letterTableViewCellHeight;
}
}
//配置sectionHeaderView的高度,字母表不需要sectionHeaderView
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
if (tableView == _tableView) {
return contentTableViewSectionHeaderHeight;
}else {
return 0.000001;
}
}
//配置sectionHeader的样式
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if (tableView == _tableView) {
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [[UIScreen mainScreen] bounds].size.width, contentTableViewSectionHeaderHeight)];
view.backgroundColor = [UIColor colorWithRed:0xf2/255.0f green:0xf2/255.0f blue:0xf2/255.0f alpha:1.0f];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(20, 0, [[UIScreen mainScreen] bounds].size.width, contentTableViewSectionHeaderHeight)];
label.font = [UIFont systemFontOfSize:14];
label.textColor = [UIColor colorWithRed:0xa2/255.0f green:0xa7/255.0f blue:0xc7/255.0f alpha:1.0f];
[view addSubview:label];
if (_dataArray.count > section) {
if (((NSArray *)_dataArray[section]).count > 0) {
LetterTableItemModel *item = _dataArray[section][0];
label.text = item.firstChar;
}
}
return view;
}else {
return [UIView new];
}
}
//分别实现两个tableView的点击事件
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (tableView == _tableView) {
//点击cell时调用,传入当前cell的model
[self.delegate clickItem:_dataArray[indexPath.section][indexPath.row]];
}else {
//更新字母表选中状态
[_letterDataArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
LetterWordItemModel *item = obj;
if (idx == indexPath.row) {
item.isCheck = YES;
}else {
item.isCheck = NO;
}
}];
[_letterTableView reloadData];
//滚动到选中字母的位置
[_dataArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSMutableArray *arr = obj;
if (arr.count > 0) {
LetterTableItemModel *item = arr[0];
//如果选中的字母,列表中没有 则不动
if ([item.firstChar isEqualToString:[((LetterWordItemModel *)_letterDataArray[indexPath.row]).content uppercaseString]]) {
[_tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:idx] atScrollPosition:UITableViewScrollPositionTop animated:NO];
*stop = YES;
}
}
}];
}
}
实现tableView的滑动协议,来实现滚动内容时,字母表能够及时的改变选中状态。
//监听列表滑动事件
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView == _tableView) {
//获取当前显示的section是哪个
NSArray <UITableViewCell *> *cellArray = [self.tableView visibleCells];
NSInteger nowSection = -1;
if (cellArray) {
UITableViewCell *cell = [cellArray firstObject];
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
nowSection = indexPath.section;
}
//同时更改字母表选中状态
if (_dataArray.count > 0) {
if (((NSMutableArray *)_dataArray[nowSection]).count > 0) {
LetterTableItemModel *item = _dataArray[nowSection][0];
[_letterDataArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
LetterWordItemModel *o = obj;
if ([item.firstChar isEqualToString:o.content]) {
o.isCheck = YES;
}else {
o.isCheck = NO;
}
}];
[_letterTableView reloadData];
}
}
}
}
初始化一个滑动字母表的提示View,可根据需求自定义一个。
_letterTipsView = [[LetterTipsView alloc] init];
[self addSubview:_letterTipsView];
实现字母表滑动手势的方法。
//滑动手势触发的方法
- (void)letterPan:(UIPanGestureRecognizer *)pan {
NSInteger state = pan.state;
if (pan.state == UIGestureRecognizerStateBegan) {
}else if (pan.state == UIGestureRecognizerStateChanged) {
//滑动中时,获取当前手指所在位置
CGPoint p = [pan locationInView:_letterTableView];
NSInteger index = (NSInteger)p.y / letterTableViewCellHeight;
//确保滑动的位置不超过最后一行
if (index > _letterDataArray.count - 1) {
index = _letterDataArray.count - 1;
}
//确保滑动的位置不小于第一行
if (index < 0) {
index = 0;
}
//根据手指位置,更改字母表选中状态
[_letterDataArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
LetterWordItemModel *item = obj;
if (idx == index) {
item.isCheck = YES;
}else {
item.isCheck = NO;
}
}];
[_letterTableView reloadData];
//滑动列表到指定位置
[_dataArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSMutableArray *arr = obj;
if (arr.count > 0) {
LetterTableItemModel *item = arr[0];
//如果选中的字母没有对应的内容 则不动
if ([item.firstChar isEqualToString:((LetterWordItemModel *)_letterDataArray[index]).content]) {
[_tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:idx] atScrollPosition:UITableViewScrollPositionTop animated:NO];
*stop = YES;
}
}
}];
//在滑动时,显示提示当前选中的是哪个字母的提示view
_letterTipsView.contentLabel.text = ((LetterWordItemModel *)_letterDataArray[index]).content;
//确定提示view的显示位置
_letterTipsView.frame = CGRectMake(CGRectGetMinX(_letterTableView.frame)-62, CGRectGetMinY(_letterTableView.frame) + index * letterTableViewCellHeight - 26 + letterTableViewCellHeight/2, 62, 52);
_letterTipsView.contentLabel.frame = CGRectMake(0, 0, CGRectGetWidth(_letterTipsView.frame), CGRectGetHeight(_letterTipsView.frame));
}else {
//手指离开时,隐藏提示View
_letterTipsView.frame = CGRectMake(0, 0, 0, 0);
}
}
调用方式
_letterView = [[LetterTableView alloc] initWithFrame:CGRectMake(0, 88, CGRectGetWidth([UIScreen mainScreen].bounds), CGRectGetHeight([UIScreen mainScreen].bounds) - 88) dataArray:dataArray delegate:self];
[self.view addSubview:_letterView];
设置好frame,添加到页面上,即可完成调用。
实现LetterTableViewDelegate协议。来监听点击cell的回调。
- (void)clickItem:(LetterTableItemModel *)item {
NSLog(@"%@",item.content);
}
具体实现demo可以参考https://github.com/zjl0624/LetterTableView
注意事项
传入的tableView的dataSource数据源数组必须为LetterTableItemModel或者他的子类。并且必须是Array嵌套一个Array的方式,控件才能正确配置cell和section。