CollectionView添加索引

在collectionView列表添加索引条, 因不像tableView的协议自带设置索引功能, 所以collectionView的索引需要自定义, 虽然麻烦点, 但也有更高的扩展性以应对更复(奇)杂(怪)的需求

我们先来看一下完成效果: ①右侧索引条 ②点击/滑动索引条屏幕中心的提示框


索引.gif

需求分解: 先将需求分解成三步

(1)绘制索引条
(2)索引条与collectionView联动效果
(3)制作提示框

这里只介绍几个用到的技术点:

(1)绘制索引条:

使用CAShapeLayerUIBezierPath实现不在view的drawRect方法中就画出想要的图形, 下图是核心绘制代码

UIBezierPath *bezierPath = [UIBezierPath bezierPath];
        [bezierPath moveToPoint:CGPointZero];
        [bezierPath addLineToPoint:CGPointMake(0, self.frame.size.height)];
        
        /*-----绘制文字部分------*/
        _letterHeight = 16;
        CGFloat fontSize = 12;
        [self.titleIndexes enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) {
            CGFloat originY = idx * _letterHeight;
            CATextLayer *ctl = [self textLayerWithSize:fontSize
                                                string:obj
                                              andFrame:CGRectMake(0, originY, self.frame.size.width, _letterHeight)];

            [self.layer addSublayer:ctl];
            
            [bezierPath moveToPoint:CGPointMake(0, originY)];
            [bezierPath addLineToPoint:CGPointMake(ctl.frame.size.width, originY)];
        }];
//绘制字体
- (CATextLayer*)textLayerWithSize:(CGFloat)size string:(NSString*)string andFrame:(CGRect)frame{
    CATextLayer *tl = [CATextLayer layer];
    [tl setFont:@"ArialMT"];
    [tl setFontSize:size];
    [tl setFrame:frame];
    [tl setAlignmentMode:kCAAlignmentCenter];
    [tl setContentsScale:[[UIScreen mainScreen] scale]];
    [tl setForegroundColor:RGB(168, 168, 168, 1).CGColor];
    [tl setString:string];
    return tl;
}

(2)索引条与collectionView联动效果:

重写响应事件的方法

#pragma mark- response事件
//手指触碰屏幕,触摸开始
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    [super touchesBegan:touches withEvent:event];
    [self sendEventToDelegate:event];
    [self.collectionDelegate collectionViewIndexTouchesBegan:self];
}

//手指在屏幕上移动
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
    [super touchesMoved:touches withEvent:event];
    [self sendEventToDelegate:event];
}

//手指离开屏幕,触摸结束
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self.collectionDelegate collectionViewIndexTouchesEnd:self];
}
//根据触摸事件的触摸点来算出点击的是第几个section
- (void)sendEventToDelegate:(UIEvent*)event{
    UITouch *touch = [[event allTouches] anyObject]; //获取触摸对象
    CGPoint point = [touch locationInView:self];   //获取当前触摸点位置
    
    NSInteger indx = ((NSInteger) floorf(point.y) / _letterHeight);  //触摸位置对应的行数
    
    if (indx< 0 || indx > self.titleIndexes.count - 1) {
        return;
    }
    
    [self.collectionDelegate collectionViewIndex:self didselectionAtIndex:indx withTitle:self.titleIndexes[indx]];
    
}

(3)中文转成拼音以进行排序操作

/**
 将中文字符串转换为拼音格式(不带声调)
 @return 返回不带声调拼音字符串
 */
- (NSString *)transformToPinyin:(NSString *)str
{
    // 空值判断
    if (str == nil || str == NULL) {
        return @"";
    }
    // 将字符串转为NSMutableString类型
    NSMutableString *string = [str mutableCopy];
    // 将字符串转换为拼音音调格式
    CFStringTransform((__bridge CFMutableStringRef)string, NULL, kCFStringTransformMandarinLatin, NO);
    // 去掉音调符号
    CFStringTransform((__bridge CFMutableStringRef)string, NULL, kCFStringTransformStripDiacritics, NO);
    // 返回不带声调拼音字符串
    return string;
}

(4)对数据进行删选(把接口提供的数据转化为我们需要的数据结构)

接口提供数据:

self.data = [NSMutableArray arrayWithArray:@[@"安卓", @"宝丽来",@"霸王别姬",@"菜鸟",@"facebook",@"菲尔可",@"飞利浦",@"谷歌",@"海尔",@"海信",@"华为",@"iPhone",@"iPad",@"Mac book",@"松下"]];

因接口数据是按字母排序过得中文名称, 过滤代码如下, 时间复杂度是O(n)

NSMutableArray *sections = [NSMutableArray array];    //创建字母数组   @[@"A", @"D", @"F", @"M", @"N", @"Z"];
    rows = [NSMutableArray array];  //创建索引数组   @[@[@"adam", @"alfred"],@[@"bo"]];
    int indexsInt = -1;    //更新索引数组元素的数值
    NSString *tempStr = @"temp";    //交换字母,用来判断该字母是否存在的数值
    for (NSString *nameStr in self.data) {
        NSString *pinyin = [self transformToPinyin:nameStr];    //将中文字符串转换为拼音格式
        if ([pinyin isEqualToString:@"zhang hong"]) {   //排错   长虹转换为拼音错误
            pinyin = @"chang hong";
        }
        NSString *testUpFirst = [pinyin capitalizedString];      //首字母大写
        NSString *firstLetterStirng = [testUpFirst substringToIndex:1];    //取出字符串第一位字母
        
        if (firstLetterStirng == tempStr) {   //判断该字母是否为上一位存在的
            [rows[indexsInt] addObject:firstLetterStirng];   //加入对应数组中
        }else{
            [sections addObject:firstLetterStirng];   //字母数组添加新字母
            NSMutableArray *array = [NSMutableArray array];   //生成新字母数组
            [array addObject:firstLetterStirng];
            [rows addObject:array];     //索引数组添加新字母数组
            indexsInt = indexsInt + 1;    //索引数组count+1
            tempStr = firstLetterStirng;    //上一位字母更换为新字母
        }
    }

引申内容

(5)数组遍历(enumerateObjectsUsingBlock)

我们常用的循环方式:
for循环 方便针对下标的处理, 适用面最广
forin 效率更高, 但无法针对下标处理, 反向遍历不方便
enumerateObjectsUsingBlock 底层通过GCD来处理并发执行事宜, 多线程来并发实现,并不保证按照顺序执行, 但效率最高

[self.titleIndexes enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) {
      //obj代表内容, idx代表循环在第几个元素(row), stop用来停止循环
      CGFloat originY = idx *12;
}];

效率(遍历速度): enumerateObjectsUsingBlock > forin > for 尽量选择更高效的遍历方式

(6)数组排序 (sortedArrayUsingComparator)

// 数组的排序
    // 定义一个数组数组
    NSArray *array = @[@(3),@(5),@(4),@(2),@(1)];
    // 对数组进行排序(升序)
    NSArray *resultAscending = [array sortedArrayUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
        NSLog(@"%@ ~ %@", obj1, obj2);
        return [obj1 compare:obj2];
    }];
    NSLog(@"对数组进行排序(正序):%@", resultAscending);
    // 对数组进行排序(降序)
    NSArray *resultDescending = [array sortedArrayUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
        NSLog(@"%@ ~ %@", obj1, obj2);
        return [obj2 compare:obj1];
    }];
    NSLog(@"对数组进行排序(降序):%@", resultDescending);
    // 对数组进行排序(乱序)
    NSArray *resultBreak = [resultAscending sortedArrayUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
        NSLog(@"%@ ~ %@", obj1, obj2);
        if (arc4random_uniform(2) == 0) { // arc4random_uniform会随机返回一个0到上界之间(不含上界)的整数。以2为上界会得到0或1,像投硬币一样
            return [obj2 compare:obj1];// descending
        } else {
            return [obj1 compare:obj2];// ascending
        }
    }];
    NSLog(@"对数组进行排序(乱序):%@", resultBreak);

参考文献:
UICollectionView 加字母索引
iOS触摸事件
iOS数组遍历 (enumerateObjectsUsingBlock)
iOS数组排序 (sortedArrayUsingComparator)

Demo下载地址:
GitHub下载

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

推荐阅读更多精彩内容