在collectionView列表添加索引条, 因不像tableView的协议自带设置索引功能, 所以collectionView的索引需要自定义, 虽然麻烦点, 但也有更高的扩展性以应对更复(奇)杂(怪)的需求
我们先来看一下完成效果: ①右侧索引条 ②点击/滑动索引条屏幕中心的提示框
需求分解: 先将需求分解成三步
(1)绘制索引条
(2)索引条与collectionView联动效果
(3)制作提示框
这里只介绍几个用到的技术点:
(1)绘制索引条:
使用CAShapeLayer
与UIBezierPath
实现不在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下载