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来实现标签组的方法,可能实现的路径和方法很多,也有更加多便捷方法和思路,上面方法如有不足之处望大家指出,或者有更优的方法,也欢迎大家来探讨。

大千世界,求同存异;相遇是缘,相识是份,相知便是“猿粪”(缘分)

原文地址:https://www.cnblogs.com/sundaysgarden/p/10559429.html

时间: 2024-10-31 15:35:29

iOS 简易型标签的实现(UICollectionView)的相关文章

iOS 简易底层 Basement-敏捷开发

iOS 简易底层 我自己在使用的Basement 简易可自定义度底层 1. 没有的东西: 网络请求  数据存储 - ASI二次开发 AF MK 各有所好. 所以不写. CoreData 使用 各有所好, 推荐Magic FMDB - 数据库相关操作 - 各有所好不写 YTKeyValue - 键值存储(数据库) 2. 写了的东西:HMSegmentControl 方便 简单 好看的Segment INTULocationManager 位置管理器 加入了iOS8.0新方法 MBProgressH

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

 iOS仿京东分类菜单之UICollectionView内容 在 上<iOS仿京东分类菜单实例实现>已经实现了大部分主体的功能,本文是针对右边集合列表进行修改扩展,使它达到分组的效果,本文涉及到的主要是UICollectionView的知识内容,左边列表的实现见上一篇文章,先看实现的效果图: 一:实体的创建 1.1分组实体的创建(tagID跟左边表格进行关联,roomArray是存放房间的数组,也就是单元格的集合) #import <Foundation/Foundation.h>

iOS开发中标签控制器的使用——UITabBarController

正文 iOS开发中标签控制器的使用——UITabBarController 一.引言 与导航控制器相类似,标签控制器也是用于管理视图控制器的一个UI控件,在其内部封装了一个标签栏,与导航不同的是,导航的管理方式是纵向的,采用push与pop切换控制器,标签的管理是横向的,通过标签的切换来改变控制器,一般我们习惯将tabBar作为应用程序的根视图控制器,在其中添加导航,导航中在对ViewController进行管理. 二.创建一个标签控制器 通过如下的步骤,我们可以很简便的创建一个TabBarCo

iOS开发——UI篇OC篇&amp;UICollectionView详解+实例

UICollectionView详解+实例 实现步骤: 一.新建两个类 1.继承自UIScrollView的子类,比如HMWaterflowView * 瀑布流显示控件,用来显示所有的瀑布流数据 2.继承自UIView的子类,比如HMWaterflowViewCell * 代表着瀑布流数据中的一个单元(一个格子) 3.总结 HMWaterflowView和HMWaterflowViewCell的关系实际上类似于 UITableView和UITableViewCell的关系 二.设计HMWater

【转】iOS,搜索标签布局

前一阵时间,看过这样一个demo,代码不多,但是简洁易懂. 转自: //  代码地址: https://github.com/iphone5solo/PYSearch //  代码地址: http://www.code4app.com/thread-11175-1-1.html /** 添加和布局标签 */ - (NSArray *)addAndLayoutTagsWithTagsContentView:(UIView *)contentView tagTexts:(NSArray<NSStri

iOS开发——新特性界面(UICollectionView)

没一款app在刚下载或者更新之后,app有些特色功能需要向用户传递,这个时候我们就要使用新特新界面,用户刚打开软件能看到各种展示图片,左右滑动还可以切换图片,那么新特性界面是如何实现的呢,下面我就用介绍下用如何代码去实现性特性界面,用的是iOS中的UICollectionView,自定义cell去实现的. CollectionViewCell.h中 #import <UIKit/UIKit.h> @interface CollectionViewCell : UICollectionViewC

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

叨逼叨 好久没更新博客了,才几个月,发生了好多事情,处理了好多事情.不变的是写代码依然在继续. 做点啥子 看看objective-c的书,学着写了个柱状图,只是练习的demo而已,iOS上的图表控件已经有非常好的解决方案了. PNChart:https://github.com/kevinzhow/PNChart 这个控件是是挺不错了,喜欢的朋友可以看看,本文很多地方借鉴了PNChart,就当学习源码也可以 动手动手 先上图先上图,配色直接用PNChart的了,还蛮喜欢这种配色风格,一直不太喜欢

iOS 去掉html标签 留下原本的字符串

做开发有的时候会遇到服务器返回的数据是html的,我们可以选择直接用webview来加载的方式去处理,当然这个方法不适用所有场景,通常我们会选择把没有必要的html标签去掉留下需要的文字信息,现在一起来看看吧. 首先提供一个html标签的字符串 NSString *html = @"<p><span style=\"font-family:宋体\">劳动是人类创造物质或精神财富的活动,有体力的,也有脑力的.我们自己是劳动者,也是别人劳动的见证者.劳动存

用一些简易的标签写一个美丽说左侧二级菜单

知识点:html标签(div,ul,a),css样式属性(浮动,定位),布局思想,js基础,逻辑思维. 对前端感兴趣或者正在学习web前端的小伙伴可以来前端群:189394454,每天会有干货分享,以及学习路线方法! html代码: <div id="Menu"> <h3>全部商品</h3> <ul> <li> <a href="#">上衣</a><a href="#