啰嗦的话
在iOS开发中,特别是一些新闻咨询类APP的开发中,经常可能会遇到需要弹出一个屏蔽窗口供用户选择将某条新闻咨询以某些理由屏蔽掉。这些窗口的特点就是:过滤的理由是后台返回的,长度和数量并不都相等,因此需要合理布局。前些天在自己的项目中也有这个需求,因此在这里简单地记录下自己的思路,当是做个笔记,如果能给有需要的人一些启发就更好了。 废话不多说,先来看下效果图(因为还没接入UI,请自觉忽略右上角大大的“+”。。。)。
实现思路
要实现这个流式标签的布局其实并不难,过程大概是这样子的:1,获取屏蔽理由数组,用于计算标签的数量;2,根据屏蔽理由字符串的长度,计算标签的长度;3,计算每个标签的位置按照顺序对标签进行排序,不够位置排列的自动换行排列。下面是相关的实现代码:
// 根据过滤理由数组创建标签并加入数组
for (int i = 0; i< _dislikeArray.count; i++) {
UIButton *btn = [[UIButton alloc] init];
[btn setTitle:_dislikeArray[i] forState:UIControlStateNormal];
btn.tag = [_dislikeArray[i];
btn.width = [self labelGetWidthWithLabel:btn maxHeight:32 font:13];
btn.height = 32 ;
[btn addTarget:self action:@selector(btnClickAction:) forControlEvents:UIControlEventTouchUpInside];
[self.btnsArrayM addObject:btn];
[self addSubview:btn];
}
// 对标签进行布局
CGFloat margin = 8;
NSMutableArray *rowFirstBtnsArrM = [NSMutableArray array]; // 存放每行的第一个button
UIButton *firstBtn = self.btnsArrayM[0]; // 对第一个button进行设置
firstBtn.x = 0;
firstBtn.y = 0;
[rowFirstBtnsArrM addObject:self.btnsArrayM[0]];
int row = 0; // 行数
// 对其他button进行设置
for (int i = 1; i < self.btnsArrayM.count; i++) { // 除去数组中第0个的剩下的其他button
UIButton *otherBtn = self.btnsArrayM[i];
int sumWidth = 0;
int start = (int)[self.btnsArrayM indexOfObject:rowFirstBtnsArrM[row]]; // 某一行的第一个button在整个数组中的index
for (int j = start; j <= i; j++) {
UIButton *rowBtn = self.btnsArrayM[j];
sumWidth += (rowBtn.width + margin); // 叠加包括某个button在内的前面所有button的宽度
}
UIButton *lastButton = self.btnsArrayM[i - 1]; // 上一行中最后一个label
if (sumWidth >= self.width) {
otherBtn.x = 0;
otherBtn.y = CGRectGetMaxY(lastButton.frame) + margin;
[rowFirstBtnsArrM addObject:otherBtn];
row ++;
} else {
otherBtn.x = sumWidth - margin - otherBtn.width;
otherBtn.y = lastButton.y;
}
}
UIButton *lastBtn = self.btnsArrayM.lastObject;
self.height = CGRectGetMaxY(lastBtn.frame);
总共的代码也就是这么多,可以说是比价简单了。思路的话也因为注释比较全相信大家也是能够看懂的。那么下面我们说下创建过程中的一些注意点吧。
注意点
1,实际开发中,我选择了使用UIButton去作为标签的基本控件而不是UILabel,原因是因为过滤理由是有点击事件的,也就是说要接受用户的点击,这一点相对于UILabel要添加手势才能接受完成的话要来得方便,并且标签还要有选中状态,这对于UIBUtton来说可以说是非常方便了;
2, 布局是通过 sumWidth 这个变量与屏蔽窗的宽度的比较来完成的,它表示的是一行所有标签的宽度叠加和。如上面代码所示,如果它的大小等于或大于屏蔽窗高度的时候,那么就将这行的最后一个标签放在下一行显示,然后计算下一行标签的位置,sumWidth清0并且继续计算下一个标签的位置;
3,rowFirstBtnsArrM存放每一行第一个标签的作用是为每一行的标签定下初始位置(变量start),只有当每一行的第一个标签确定位置了,才能将后面的标签宽度进行叠加进而进行排列;
4,初始化这种标签视图的方式大概可以分为两种:一种是根据过滤理由去进行初始化创建视图(初始化的时候同时布局标签),一种是先创建视图,然后在设置过滤数组的时候对标签进行布局。本质上来说应该是没什么区别的,只是要注意一下相关的生命周期方法,免得布局的方法不被适时调用造成显示上的错误。
5,在实际项目中,我遇到一个问题:在首次点击屏蔽窗的时候,标签是一个一行排列的,但是当我第二次点击的时候,又是正常的布局了。代码是和demo一致的,并且demo是正常显示的不存在这个问题,唯一的区别就是:demo使用的是初始化布局,而项目因为涉及网络请求,用的是设置数据的时候布局,但是代码检查后我觉得是没什么问题的,数据也是没问题的,可以说是相当诡异了。如果大家知道问题在哪里的话,欢迎告知一下,不胜感激啦。
最后
基本上也就这些了吧,如果有什么错误的地方也希望大家可以提出来,谢谢各位!