iOS 简易型标签的实现(UICollectionView)
https://blog.csdn.net/sinat_39362502/article/details/80900984
2018年07月03日 16:49:05 Recorder_MZou 阅读数:2079
接到一个需求就是要实现标签组的显示和选择,如下图所示:
一开始感觉没有什么头绪,参考网上各种demo,发现大部分的demo都是以自绘制标签为主实现标签的长度计算和自动换行,但是这样需要实现的计算量就非常大,对于一部分参考和后期维护起来就非常麻烦,稍微修改错一个参数,导致计算不准确,这就不太好实现。
但是想了一下我们常用的系统控件中,是否有相关的控件可以实现呢?第一个想法就让我想到了UICollectionView,既然UICollectionView能实现瀑布流,为什么标签这种无规则的界面不能实现呢?,于是就开始初步搭建:
首先,先了解在UICollectionView中如何能或者每一个cell的大小,想到我们常用的UITableView的操作,其实两者的用法基本也是一样的,所以在UICollectionViewDelegate中就有
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
NSString * containString = @"你想知道怎么实现宽度计算吗?";
CGFloat width = [NSString getWidthWithText:containString height:30 font:13];
return CGSizeMake(width + 25, 30.0f);
}
这个方法能手动设置cell的大小。
既然能手动设置cell的大小了,那这样实现起来就容易了,按照正常的流程设置UICollectionView的数据源方法和相关的代理方法即可。
接下来的就是一个封装好的类,即计算String的宽度:
/**
根据高度度求宽度
@param text 计算的内容
@param height 计算的高度
@param font 字体大小
@return 返回宽度
*/
+ (CGFloat)getWidthWithText:(NSString *)text height:(CGFloat)height font:(CGFloat)font
{
//加上判断,防止传nil等不符合的值,导致程序奔溃
if (text == nil || [text isEqualToString:@""]){
text = @"";
}
if (font <= 0){
font = 13;
}
if (height < 0){
height = 0;
}
CGRect rect = [text boundingRectWithSize:CGSizeMake(MAXFLOAT, height)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:font]}
context:nil];
return rect.size.width;
}
这样大概的设置,大概即可实现在UICollectionView中的每一个cell的大小,而最重要的一步就来了,如何设置UICollectionView的布局呢?,这个就是整个标签组最为重要的一部分。
1.如何实现cell的布局位置要靠左对齐,并实现到屏幕最右边时能自动换行;
2.如何实现每一个不同长度的cell的具体距离和到不会被UICollectionViewFlowLayout默认数据自动拉伸到屏幕平分呢;
这个时候,我们就需要重写UICollectionViewFlowLayout,首先要获取相关容器的宽度,或者每一个cell的宽度,然后通过计算每一个行cell的宽度和间隙相加的和是否大于容器的宽度,如果大于,即可更换其行数,以下就是 .m文件的直接代码展示(参考来源 @Giovanni Lodi):
@interface UICollectionViewLayoutAttributes (LeftAligned)
- (void)leftAlignFrameWithSectionInset:(UIEdgeInsets)sectionInset;
@end
@implementation UICollectionViewLayoutAttributes (LeftAligned)
- (void)leftAlignFrameWithSectionInset:(UIEdgeInsets)sectionInset
{
CGRect frame = self.frame;
frame.origin.x = sectionInset.left;
self.frame = frame;
}
@end
#pragma mark -
@implementation UICollectionViewLeftAlignedLayout
#pragma mark - UICollectionViewLayout
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray *originalAttributes = [super layoutAttributesForElementsInRect:rect];
NSMutableArray *updatedAttributes = [NSMutableArray arrayWithArray:originalAttributes];
for (UICollectionViewLayoutAttributes *attributes in originalAttributes) {
if (!attributes.representedElementKind) {
NSUInteger index = [updatedAttributes indexOfObject:attributes];
updatedAttributes[index] = [self layoutAttributesForItemAtIndexPath:attributes.indexPath];
}
}
return updatedAttributes;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes* currentItemAttributes = [[super layoutAttributesForItemAtIndexPath:indexPath] copy];
UIEdgeInsets sectionInset = [self evaluatedSectionInsetForItemAtIndex:indexPath.section];
BOOL isFirstItemInSection = indexPath.item == 0;
CGFloat layoutWidth = CGRectGetWidth(self.collectionView.frame) - sectionInset.left - sectionInset.right;
if (isFirstItemInSection) {
[currentItemAttributes leftAlignFrameWithSectionInset:sectionInset];
return currentItemAttributes;
}
NSIndexPath* previousIndexPath = [NSIndexPath indexPathForItem:indexPath.item-1 inSection:indexPath.section];
CGRect previousFrame = [self layoutAttributesForItemAtIndexPath:previousIndexPath].frame;
CGFloat previousFrameRightPoint = previousFrame.origin.x + previousFrame.size.width;
CGRect currentFrame = currentItemAttributes.frame;
CGRect strecthedCurrentFrame = CGRectMake(sectionInset.left,
currentFrame.origin.y,
layoutWidth,
currentFrame.size.height);
// if the current frame, once left aligned to the left and stretched to the full collection view
// width intersects the previous frame then they are on the same line
BOOL isFirstItemInRow = !CGRectIntersectsRect(previousFrame, strecthedCurrentFrame);
if (isFirstItemInRow) {
// make sure the first item on a line is left aligned
[currentItemAttributes leftAlignFrameWithSectionInset:sectionInset];
return currentItemAttributes;
}
CGRect frame = currentItemAttributes.frame;
frame.origin.x = previousFrameRightPoint + [self evaluatedMinimumInteritemSpacingForSectionAtIndex:indexPath.section];
currentItemAttributes.frame = frame;
return currentItemAttributes;
}
- (CGFloat)evaluatedMinimumInteritemSpacingForSectionAtIndex:(NSInteger)sectionIndex
{
if ([self.collectionView.delegate respondsToSelector:@selector(collectionView:layout:minimumInteritemSpacingForSectionAtIndex:)]) {
id<UICollectionViewDelegateLeftAlignedLayout> delegate = (id<UICollectionViewDelegateLeftAlignedLayout>)self.collectionView.delegate;
return [delegate collectionView:self.collectionView layout:self minimumInteritemSpacingForSectionAtIndex:sectionIndex];
} else {
return self.minimumInteritemSpacing;
}
}
- (UIEdgeInsets)evaluatedSectionInsetForItemAtIndex:(NSInteger)index
{
if ([self.collectionView.delegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)]) {
id<UICollectionViewDelegateLeftAlignedLayout> delegate = (id<UICollectionViewDelegateLeftAlignedLayout>)self.collectionView.delegate;
return [delegate collectionView:self.collectionView layout:self insetForSectionAtIndex:index];
} else {
return self.sectionInset;
}
}
@end
那么既然最困难的一步就是布局的问题已经解决了,那整体的页面显示已经完成了,那接下来就是数据的处理了,那要如何实现下图显示?
标签选择.gif
那首先要思考两个问题,如果通过对数据源的设置实现UICollectionView中每一组选中cell或者不选中cell,多选和单选的区别,此时我想到的一个方法就是给数据源一个属性,标识该cell是否选中:
/**
是否选中
*/
@property (nonatomic, assign) BOOL isSelect;
通过数据源的属性来控制cell内部选中的控件的颜色和状态
[_tagBtn setTitle:model.name forState: UIControlStateNormal];
UIColor *containStringColor = model.color == 0 ? tagBlueColor : model.color == 1 ? tagRedColor : model.color == 2 ? tagGreenColor : model.color == 3 ? tagYellowColor : model.color == 4 ? tagVioletColor : tagIndigoColor;
if (model.isSelect == YES)
{
[_tagBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
_tagBtn.backgroundColor = containStringColor;
}
else
{
[_tagBtn setTitleColor:labelBlackColor forState:UIControlStateNormal];
_tagBtn.backgroundColor = HEXCOLOR(0xeeeeee);
}
那controller中的数据该怎么判断是否选中和未选择,然后实现给数据源的属性选中呢?具体思路就是:需要进行遍历数据源中的所有模型进行判断和赋值选中状态,实现方法如下:
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
[collectionView deselectItemAtIndexPath:indexPath animated:NO];
PBCTagGroundModel *groundModel = self.listArrM[indexPath.section];
DLog(@"indexPath.Seciont = %zd,row = %zd",indexPath.section,indexPath.row);
//替换成可选模式
for (int i = 0; i < groundModel.tags.count; ++i)
{
PBCTagModel *model = groundModel.tags[i];
if (indexPath.row == i)
{
if (model.isSelect == YES)
{
model.isSelect = NO;
}
else
{
model.isSelect = YES;
}
[groundModel.tags replaceObjectAtIndex:i withObject:model];
}
}
[self.listArrM replaceObjectAtIndex:indexPath.section withObject:groundModel];
[self.collectionView reloadData];
}
既然每一组的cell都实现了选中和未选中状态了,那如何实现判断全选和不选中的状态呢?这里就需要给UICollectionView的头部设置一个点击事件,并且也需要对数据源中的所有数据进行遍历,对数据的选中状态进行选中和未选中的属性赋值。
那么问题来了,如何这是UICollectionView的头部呢?这个和UITableView的操作其他大有雷同:
首先,要先注册UICollectionView的头部:
//注册头视图
[collectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"UICollectionViewHeader"];
然后实现collectionView的头部尾部设置代理
//设置头视图的大小
-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section{
return CGSizeMake([UIScreen mainScreen].bounds.size.width, 44);
}
//创建头视图
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView
viewForSupplementaryElementOfKind:(NSString *)kind
atIndexPath:(NSIndexPath *)indexPath {
NSString *indentifierString = @"UICollectionViewHeader";
UICollectionReusableView *headView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader
withReuseIdentifier:indentifierString
forIndexPath:indexPath];
headView.backgroundColor = [UIColor whiteColor];
//此处操作是为了防止头部视图重复使用(从缓存机制中取出),导致重叠视图,如要移除此前设置的视图再进行绘制
for (UIView *subView in headView.subviews)
{
[subView removeFromSuperview];
}
//设置相关视图
//.....此处设置
return headView;
}
设置完头部视图了,那具体的数据操作就是遍历数据修改数据源状态了,代码如下:
/**
点击选择组便签
@param btn 组标签
*/
- (void)clickSelectAllBtn:(UIButton *)btn
{
btn.selected = !btn.selected;
PBCTagGroundModel *groundModel = self.listArrM[btn.tag];
//替换成可选模式
for (int i = 0; i < groundModel.tags.count; ++i)
{
PBCTagModel *model = groundModel.tags[i];
if (btn.selected == YES)
{
model.isSelect = YES;
}
else
{
model.isSelect = NO;
}
[groundModel.tags replaceObjectAtIndex:i withObject:model];
}
[self.listArrM replaceObjectAtIndex:btn.tag withObject:groundModel];
[self.collectionView reloadData];
}
那么对数据源也处理完了,整体的标签组也是实现了。
其实整体设置标签组,最大的难度是在于UICollectionViewFlowLayout的重写和计算,如果这一步解决了,整体的思路很清晰,可以直接解决问题。
以上就是通过UICollectionView来实现标签组的方法,可能实现的路径和方法很多,也有更加多便捷方法和思路,上面方法如有不足之处望大家指出,或者有更优的方法,也欢迎大家来探讨。
大千世界,求同存异;相遇是缘,相识是份,相知便是“猿粪”(缘分)
iOS 简易型标签的实现(UICollectionView)的更多相关文章
- 【iOS系列】- iOS吸附效果的实现 之 UICollectionView的使用全解
[iOS系列]- iOS吸附效果的实现 之 UICollectionView的使用全解 UICollectionView可以做很多的布局,在iOS开发中较为重要,所以这里就以实例来讲解UICollec ...
- 【转】iOS,搜索标签布局
前一阵时间,看过这样一个demo,代码不多,但是简洁易懂. 转自: // 代码地址: https://github.com/iphone5solo/PYSearch // 代码地址: http:/ ...
- iOS仿京东分类菜单之UICollectionView内容
在上<iOS仿京东分类菜单实例实现>已经实现了大部分主体的功能,本文是针对右边集合列表进行修改扩展,使它达到分组的效果,本文涉及到的主要是UICollectionView的知识内容,左边列 ...
- iOS开发——UI篇OC篇&UICollectionView详解+实例
UICollectionView详解+实例 实现步骤: 一.新建两个类 1.继承自UIScrollView的子类,比如HMWaterflowView * 瀑布流显示控件,用来显示所有的瀑布流数据 2. ...
- 去除ios系统a标签点击时的灰色背景
使用图片作为a标签的点击按钮时,当触发touchstart的时候,往往会有一个灰色的背景,想要去掉的话可以用下面这种方式 a,a:hover,a:active,a:visited,a:link,a:f ...
- iOS简易柱状图(带动画)--新手入门篇
叨逼叨 好久没更新博客了,才几个月,发生了好多事情,处理了好多事情.不变的是写代码依然在继续. 做点啥子 看看objective-c的书,学着写了个柱状图,只是练习的demo而已,iOS上的图表控件已 ...
- IOS动态自适应标签实现
先上效果图 设计要求 1.标签的宽度是按内容自适应的 2.一行显示的标签个数是动态的,放得下就放,放不下就换行 3.默认选中第一个 4.至少选中一个标签 实现思路 首先我们从这个效果上来看,这个标签是 ...
- iOS使用XZMRefresh实现UITableView或UICollectionView横向刷新
https://blog.csdn.net/u013285730/article/details/50615551?utm_source=blogxgwz6 XZMRefresh The easies ...
- app混合开发 fastlick.js 在ios上 input标签点击 不灵敏 处理
ios11 上有这个问题 而老版本的ios没有 会出现这个的原因是使用fastclick.js点击后input没有获取焦点,所以只需要在fasyclick的源码的这个位置 可以直接在源码内搜索关键字找 ...
随机推荐
- javascript小实例,阻止浏览器默认行为,真的能阻止吗?支持IE和标准浏览器的阻止默认行为的方法
看到这标题,是不是有点逆天的感觉,总感觉好狂拽炫酷,耳边隐隐约约传来一个声音:你这么叼,你咋不上天呢! ~~ 额,好吧! 话入正题,我为什么会提出这么一个问题呢? 阻止浏览器默认行为,真的能阻止吗?那 ...
- c#调用com组件,程序 发生意外<hr=0x80020009>
引用dll,确认dll没有问题,版本正确,可是一直报发生意外,没有任何其他提示. 解决方案: 看dll引用选项配置 复制到本地:设为true,我的就是false; 嵌入互操作类型:false,如果是t ...
- 学JAVA第五天,今天困得要死
好不容易坚持到第五天了,继续继续!!! 今天老师没有讲JAVA的for循环,倒是讲了HTML的相关内容: 讲了JAVA代码怎么在HTML中运行. 只要在HTML加入这个 background-colo ...
- TiDB
由于目前的项目把mysql换成了TiDb,所以特意来了解下tidb.其实也不能说换,由于tidb和mysql几乎完全兼容,所以我们的程序没有任何改动就完成了数据库从mysql到TiDb的转换,TiDB ...
- Salesforce 小知识:大量“子记录”的处理方法
大量"子记录"的存放 例子:系统中导入了很多"联系人"(Contact)记录,它们没有具体所属的"客户"(Account)记录.那么我们就要 ...
- 仿9GAG制作过程(四)
有话要说: 这次主要讲述主页面下拉刷新和上拉加载功能的实现. 主要是使用了SwipeRefreshLayout的布局方式,并在此基础上通过RecyclerView的特性增加了上拉加载的功能. 成果: ...
- vs文件上传失败--超过最大字符限制
一.问题 在文件上传时,会遇到大文件上传失败. >F12查看报错网络请求返回结果 >问题分析 由于vs上传文件默认的字符大小控制. 二.解决方法 >在web.config中修改或添加 ...
- MVC Controller return 格式分类及用法
概述 所看到的Action都是return View();我们可以看作这个返回值用于解析一个aspx文件.而它的返回类型是ActionResult如 public ActionResult Index ...
- 从0开始的Python学习006流程控制
流程控制语句 Python中有三种控制流程语句: if.for.和while. if语句 使用if语句来校验一个条件,如果条件为真(True),运行if-块,如果为假(False),运行else-块. ...
- 【公众号系列】SAP的新零售
公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[公众号系列]SAP的新零售 写在前面 还是 ...