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)的更多相关文章

  1. 【iOS系列】- iOS吸附效果的实现 之 UICollectionView的使用全解

    [iOS系列]- iOS吸附效果的实现 之 UICollectionView的使用全解 UICollectionView可以做很多的布局,在iOS开发中较为重要,所以这里就以实例来讲解UICollec ...

  2. 【转】iOS,搜索标签布局

    前一阵时间,看过这样一个demo,代码不多,但是简洁易懂. 转自: //  代码地址: https://github.com/iphone5solo/PYSearch //  代码地址: http:/ ...

  3. iOS仿京东分类菜单之UICollectionView内容

    在上<iOS仿京东分类菜单实例实现>已经实现了大部分主体的功能,本文是针对右边集合列表进行修改扩展,使它达到分组的效果,本文涉及到的主要是UICollectionView的知识内容,左边列 ...

  4. iOS开发——UI篇OC篇&UICollectionView详解+实例

    UICollectionView详解+实例 实现步骤: 一.新建两个类 1.继承自UIScrollView的子类,比如HMWaterflowView * 瀑布流显示控件,用来显示所有的瀑布流数据 2. ...

  5. 去除ios系统a标签点击时的灰色背景

    使用图片作为a标签的点击按钮时,当触发touchstart的时候,往往会有一个灰色的背景,想要去掉的话可以用下面这种方式 a,a:hover,a:active,a:visited,a:link,a:f ...

  6. iOS简易柱状图(带动画)--新手入门篇

    叨逼叨 好久没更新博客了,才几个月,发生了好多事情,处理了好多事情.不变的是写代码依然在继续. 做点啥子 看看objective-c的书,学着写了个柱状图,只是练习的demo而已,iOS上的图表控件已 ...

  7. IOS动态自适应标签实现

    先上效果图 设计要求 1.标签的宽度是按内容自适应的 2.一行显示的标签个数是动态的,放得下就放,放不下就换行 3.默认选中第一个 4.至少选中一个标签 实现思路 首先我们从这个效果上来看,这个标签是 ...

  8. iOS使用XZMRefresh实现UITableView或UICollectionView横向刷新

    https://blog.csdn.net/u013285730/article/details/50615551?utm_source=blogxgwz6 XZMRefresh The easies ...

  9. app混合开发 fastlick.js 在ios上 input标签点击 不灵敏 处理

    ios11 上有这个问题 而老版本的ios没有 会出现这个的原因是使用fastclick.js点击后input没有获取焦点,所以只需要在fasyclick的源码的这个位置 可以直接在源码内搜索关键字找 ...

随机推荐

  1. 【.NET Core项目实战-统一认证平台】第九章 授权篇-使用Dapper持久化IdentityServer4

    [.NET Core项目实战-统一认证平台]开篇及目录索引 上篇文章介绍了IdentityServer4的源码分析的内容,让我们知道了IdentityServer4的一些运行原理,这篇将介绍如何使用d ...

  2. 【转载】C#代码开发过程中如何快速比较两个文件夹中的文件的异同

    在日常的使用电脑的过程中,有时候我们需要比较两个文件夹,查找出两个文件夹中不同的文件以及文件中不同的内容信息,进行内容的校对以及合并等操作.其实使用Beyond Compare软件即可轻松比较,Bey ...

  3. IIS配置HTTPS

    1,新建网站,选中类型为 https,然后更改SSL证书为你配置的SSL证书, 对于SSL证书的配置是这样的 点开第二步,然后点击 创建自签名证书 确定以后点开网站看到有个SSL, 双击进去,再选中 ...

  4. InnoSetup 客户端程序打包教程

    之前介绍过InstallShield打包工具,本文再介绍更加方便的打包工具Inno Setup Inno Setup相对来说,比InstallShield更容易使用,不需要在VS中创建项目,只要提供D ...

  5. Python re 模块

    Python re 模块 TOC 介绍 作用 正则表达式语法 贪婪和非贪婪 普通字符和特殊字符 分组(比较重要) re modul level 方法 正则表达式对象 匹配对象 常用例子 注意事项 Ja ...

  6. css direction 属性简介与实际应用。

    目前正在用vue构建组件库.写到弹框的时候没想到按钮的顺序问题,但是在应用中,确实会有选项按钮顺序不同的情况发生,但是又想共用一个组件.那么问题就出现了.后来看到了这篇文章,才茅塞顿开. direct ...

  7. C#学习笔记---C#操作SQL数据库

    C#操作SQL数据库 Connection(连接)对象 连接字符串: 形式1.”server=;uid=;pwd=;database=” 形式2.”server=;Intergrated Securi ...

  8. 【原】Java学习笔记020 - 面向对象

    package cn.temptation; public class Sample01 { public static void main(String[] args) { // 成员方法的参数列表 ...

  9. 关于SNMP的MIB文件的语法简述

    源地址:https://blog.csdn.net/carechere/article/details/51236184 SNMP协议的MIB文件的常见宏定义的描述: 对MIB文件中一些常见的宏定义的 ...

  10. SQLServer之创建唯一非聚集索引

    创建唯一非聚集索引典型实现 唯一索引可通过以下方式实现: PRIMARY KEY 或 UNIQUE 约束 在创建 PRIMARY KEY 约束时,如果不存在该表的聚集索引且未指定唯一非聚集索引,则将自 ...