iOS实现类似通讯录的字母滚动功能

实现效果

111.png

实现思路

初始化字母表数组

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。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容