iOS UICollectionView实现动态标签(单选、多选)

项目中我们经常会遇到标签动态展示的问题,有时我们也需要实现单选或者多选的功能

<1> 针对标签动态展示,我们解决的核心办法就是动态计算文本宽度

即:标签宽度=文本宽度+左右间距

核心代码:

#pragma mark -- UICollectionViewDelegateFlowLayout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    TaskValueModel *model = self.model.orgRelationCollection[indexPath.row];
    NSString *str = ObjErrorCheck(model.value);
   // 核心代码 动态宽度计算
   //  LL_ScreenWidth -74 (文本最大宽度)最大就是一行放置一个
    CGRect itemFrame = [str boundingRectWithSize:CGSizeMake(LL_ScreenWidth -74, 14) options: NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:14]} context:nil];
   //  item的宽度 文本宽度+左右间距合计24
    CGFloat width = itemFrame.size.width + 24;
    //  item的size
    return CGSizeMake(width, 32);
}

<2> 针对单选或者多选,核心就是在标签模型上的selected标记,然后根据操作改变这个值

单选核心代码

#pragma mark -- UICollectionViewDelegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
    //  单选功能
    TaskValueModel *model = self.model.orgRelationCollection[indexPath.row];
    //  选中再次点击取消选中
    if (model.selected) {
        model.selected = !model.selected;
    //  未选中点击选中,同时确保其它未选中
    } else {
        [self.model.orgRelationCollection enumerateObjectsUsingBlock:^(TaskValueModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            if (model.id == obj.id) {
                obj.selected  = YES;
            }else {
                obj.selected  = NO;
            }
        }];
    }
    [self.collectionView reloadData];
    
}

多选核心代码

#pragma mark -- UICollectionViewDelegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
    //    多选实现
    TaskValueModel *model = self.model.valueTypeCollection[indexPath.row];
    model.selected = !model.selected;
    [self.collectionView reloadData];
    
}

 注意点:

文本的最大宽度LL_ScreenWidth -74 (文本最大宽度)最大就是一行放置一个

计算高度时的最大宽度(xOffset + 当前item的宽):LL_ScreenWidth -50  最大宽度(collectionView的宽度)

 

今天我就简述下使用UICollectionView实现上述功能,

1、自定义布局 LeftAlignFlowLayout

2、自定义标签 ValueTypeCollectionViewCell

3、标签对应的模型 TaskValueModel(因为我们需要在模型中记录选中未选状态,并且会做修改,所以不建议使用NSDictionary,不然修改会比较麻烦

4、多选功能(未选点击选中,再次点击取消选中

5、单选功能(未选点击选中且保证其它是未选中,再次点击取消选中ValueTypeTableViewCell

效果如图:

 

 

 

话不多说,直接上代码

一、自定义布局LeftAlignFlowLayout

LeftAlignFlowLayout.h

//
//  LeftAlignFlowLayout.h
//  CollectionViewDemo
//
//  Created by LJY on 2017/8/24.
//  Copyright © 2017年 LJY. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface LeftAlignFlowLayout : UICollectionViewFlowLayout

/**
 *  可以通过UICollectionViewFlowLayout的属性,或者UICollectionViewDelegateFlowLayout的方法设置布局属性。皆不设置
 *  采用系统默认值。
 */
+ (instancetype) leftAlignLayoutWithDelegate:(id<UICollectionViewDelegateFlowLayout>) delegate;

@end

LeftAlignFlowLayout.m

//
//  LeftAlignFlowLayout.m
//  CollectionViewDemo
//
//  Created by LJY on 2017/8/24.
//  Copyright © 2017年 LJY. All rights reserved.
//

#import "LeftAlignFlowLayout.h"

@interface LeftAlignFlowLayout ()

@property (nonatomic, weak) id<UICollectionViewDelegateFlowLayout> delegate;
@property (nonatomic, strong) NSMutableArray *arrForItemAtrributes;

@end

@implementation LeftAlignFlowLayout

+ (instancetype) leftAlignLayoutWithDelegate:(id<UICollectionViewDelegateFlowLayout>) delegate
{
    LeftAlignFlowLayout* layout = [[LeftAlignFlowLayout alloc] init];
    layout.delegate = delegate;
    return layout;
}

#pragma mark Override method
- (void)prepareLayout
{
    [super prepareLayout];
    CGFloat xForItemOrigin = self.sectionInsetCustomer.left;
    CGFloat yForItemOrigin = self.sectionInsetCustomer.top;
    CGFloat itemHeight = [self itemSizeCustomer:[NSIndexPath indexPathForRow:0 inSection:0]].height;
    CGFloat xOffset = self.sectionInsetCustomer.left;
    NSUInteger numberOfItems = [self.collectionView numberOfItemsInSection:0];
    NSUInteger numberOfRows = 0;
    self.arrForItemAtrributes = [NSMutableArray arrayWithCapacity:numberOfItems];
    // 为每个item确定LayoutAttribute属性,同时将这些属性放入布局数组中 
    for(int i = 0; i <numberOfItems; i++)
    {
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        UICollectionViewLayoutAttributes *layoutArr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
        CGFloat itemWidth = [self itemSizeCustomer:indexPath].width;;
        //xOffset + 当前item的宽 <= 最大宽度
        if ((xOffset + itemWidth) <= (self.collectionView.bounds.size.width - self.sectionInsetCustomer.right - self.sectionInsetCustomer.left))
        {
            //设置x
            xForItemOrigin = xOffset;
            xOffset += itemWidth + self.minimumInteritemSpacingCustomer;
        }
        else
        {
            //换行
            xForItemOrigin = self.sectionInsetCustomer.left;
            xOffset  = self.sectionInsetCustomer.left + itemWidth + self.minimumInteritemSpacingCustomer;
            numberOfRows++;
        }
        // 设置y
        yForItemOrigin = self.sectionInsetCustomer.top + (itemHeight+ self.minimumLineSpacingCustomer) * numberOfRows;
        layoutArr.frame = CGRectMake(xForItemOrigin, yForItemOrigin, itemWidth, itemHeight);
        [self.arrForItemAtrributes addObject:layoutArr];
    }
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    return self.arrForItemAtrributes;
}

#pragma mark - Inner method
/**
 *  default 10
 */
- (CGFloat)minimumLineSpacingCustomer
{
    if (self.delegate && [self.delegate respondsToSelector:@selector(collectionView:layout:minimumLineSpacingForSectionAtIndex:)])
    {
        return [self.delegate collectionView:self.collectionView layout:self minimumLineSpacingForSectionAtIndex:0];
    }
    else
    {
        return self.minimumLineSpacing;
    }
}
/**
 *  default 10
 */
- (CGFloat)minimumInteritemSpacingCustomer
{
    if (self.delegate && [self.delegate respondsToSelector:@selector(collectionView:layout:minimumInteritemSpacingForSectionAtIndex:)])
    {
        return [self.delegate collectionView:self.collectionView layout:self minimumInteritemSpacingForSectionAtIndex:0];
    }
    else
    {
        return self.minimumInteritemSpacing;
    }
}
/**
 *  default UIEdgeInsetsZero
 */
- (UIEdgeInsets)sectionInsetCustomer
{
    if (self.delegate && [self.delegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)])
    {
        return [self.delegate collectionView:self.collectionView layout:self insetForSectionAtIndex:0];
    }
    else
    {
        return self.sectionInset;
    }

}
/**
 *  default {50, 50}
 */
- (CGSize)itemSizeCustomer:(NSIndexPath *)indexPath
{
    if (self.delegate && [self.delegate respondsToSelector:@selector(collectionView:layout:sizeForItemAtIndexPath:)])
    {
        return [self.delegate collectionView:self.collectionView layout:self sizeForItemAtIndexPath:indexPath];
    }
    else
    {
        return self.itemSize;
    }
}
/**
 *  default CGSizeZero
 */
- (CGSize)headerReferenceSizeCustomer
{
    if (self.delegate && [self.delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)])
    {
        return [self.delegate collectionView:self.collectionView layout:self referenceSizeForHeaderInSection:0];
    }
    else
    {
        return self.headerReferenceSize;
    }
}
/**
 *  default CGSizeZero
 */
- (CGSize)footerReferenceSizeCustomer
{
    if (self.delegate && [self.delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)])
    {
        return [self.delegate collectionView:self.collectionView layout:self referenceSizeForFooterInSection:0];
    }
    else
    {
        return self.footerReferenceSize;
    }
}

@end

二、自定义标签ValueTypeCollectionViewCell

ValueTypeCollectionViewCell.h

#import <UIKit/UIKit.h>
#import "iCCarrerCircleTaskDetailsNewModel.h"
NS_ASSUME_NONNULL_BEGIN
static NSString  *const ValueTypeCollectionViewCellReused     = @"ValueTypeCollectionViewCell";
@interface ValueTypeCollectionViewCell : UICollectionViewCell
- (void)configCell:(TaskValueModel *)model;
@end

ValueTypeCollectionViewCell.m

#import "ValueTypeCollectionViewCell.h"

@interface ValueTypeCollectionViewCell ()
@property (nonatomic, strong) UIView *bgView;
@property (nonatomic, strong) UILabel *tagLabel;
@property (nonatomic, strong) UIImageView *rightIcon;
@end

@implementation ValueTypeCollectionViewCell

#pragma mark - System

- (instancetype)initWithFrame:(CGRect)frame {
     
    self = [super initWithFrame:frame];
    if (self) {
        [self initData];
        [self initUI];
    }
    return self;
}

#pragma mark - Init Data

- (void)initData {
    
}

#pragma mark - Init UI

- (void)initUI {
    
    self.backgroundColor = [UIColor clearColor];
    
    [self.contentView addSubview:self.bgView];
    [self.bgView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.contentView);
    }];
    
    [self.bgView addSubview:self.rightIcon];
    [self.rightIcon mas_makeConstraints:^(MASConstraintMaker *make) {
        make.right.equalTo(self.bgView);
        make.top.equalTo(self.bgView);
        make.width.height.mas_equalTo(19);
    }];
    
    [self.bgView addSubview:self.tagLabel];
    [self.tagLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.equalTo(self.bgView);
    }];
    
}

- (UIView *)bgView {
    if (!_bgView) {
        _bgView = [[UIView alloc] init];
        [_bgView cornerAngel:5];
    }
    return _bgView;
}

- (UILabel *)tagLabel {
    if(!_tagLabel){
        _tagLabel = [[UILabel alloc]init];
        _tagLabel.font = FF_PFR_ICOME(14);
    }
    return _tagLabel;
}

- (UIImageView *)rightIcon {
    if(!_rightIcon){
        _rightIcon = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"task_value_sel"]];
    }
    return _rightIcon;
}

#pragma mark - Reload View
- (void)configCell:(TaskValueModel *)model {
    self.tagLabel.text = ObjErrorCheck(model.value);
    
    if (model.allowdClick) {
        
        if (model.selected) {
//            选中样式
            self.bgView.backgroundColor = CC_ICOME(@"#3480FF1A");
            [self.bgView borderWidth:ONE_PIXEL andBorderColor:CC_ICOME(@"#3480FF33")];
            self.tagLabel.textColor = CC_ICOME(@"#3480FF");
            self.rightIcon.hidden = NO;
            
        } else {
//         未选中样式
            self.bgView.backgroundColor = XZWL_COLOR_FFFFFF;
            [self.bgView borderWidth:ONE_PIXEL andBorderColor:CC_ICOME(@"#BBBBBB")];
            self.tagLabel.textColor = CC_ICOME(@"#666666");
            self.rightIcon.hidden = YES;
        }
        
    } else {
//        不可点击样式
        self.bgView.backgroundColor = CC_ICOME(@"#3480FF1A");
        [self.bgView borderWidth:ONE_PIXEL andBorderColor:CC_ICOME(@"#3480FF33")];
        self.tagLabel.textColor = CC_ICOME(@"#3480FF");
        self.rightIcon.hidden = YES;
    }
    
}

@end

三、标签对应模型TaskValueModel

TaskValueModel.h

@protocol TaskValueModel
@end
@interface TaskValueModel : JSONModel
@property (nonatomic, copy) NSString *value;//
@property (nonatomic, assign) NSInteger id;//
@property (nonatomic, assign) BOOL selected;//
@property (nonatomic, assign) BOOL allowdClick;//
@end

四、单选功能卡片ValueTypeTableViewCell

ValueTypeTableViewCell.h

#import "BaseTableViewCell.h"
#import "iCCarrerCircleTaskDetailsNewModel.h"
NS_ASSUME_NONNULL_BEGIN

@interface ValueTypeTableViewCell : BaseTableViewCell
+ (CGFloat)getCellHeight:(iCCarrerCircleTaskDetailsNewModel *)model;

- (void)configCell:(iCCarrerCircleTaskDetailsNewModel *)model;
@end

ValueTypeTableViewCell.m

#import "ValueTypeTableViewCell.h"
#import "ValueTypeCollectionViewCell.h"
#import "LeftAlignFlowLayout.h"

@interface ValueTypeTableViewCell ()<UICollectionViewDelegateFlowLayout,UICollectionViewDelegate, UICollectionViewDataSource>
@property (nonatomic, strong) UIView *bgView;
@property (nonatomic, strong) UIImageView *LImage;
@property (nonatomic, strong) UILabel *LLab;
@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, strong) iCCarrerCircleTaskDetailsNewModel *model;
@end

@implementation ValueTypeTableViewCell
#pragma mark - System

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        [self initUI];
    }
    return self;
}

#pragma mark - Init Data

- (void)initData {
}

#pragma mark - Init UI

- (void)initUI {
    self.accessoryType = UITableViewCellAccessoryNone;
    self.selectionStyle = UITableViewCellSelectionStyleNone;
    self.backgroundColor = [UIColor clearColor];
    
    [self.contentView addSubview:self.bgView];
    [self.bgView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.contentView.mas_top);
        make.left.equalTo(self.contentView.mas_left).offset(10);
        make.right.equalTo(self.contentView.mas_right).offset(-10);
        make.bottom.equalTo(self.contentView);
    }];
    
    [self.bgView addSubview:self.LImage];
    [self.LImage mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.bgView).offset(15);
        make.top.equalTo(self.bgView).offset(17);
        make.height.mas_equalTo(17);
        make.width.mas_equalTo(16);
    }];
    
    [self.bgView addSubview:self.LLab];
    [self.LLab mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.bgView).offset(44);
        make.centerY.equalTo(self.LImage);
    }];
    
    [self.bgView addSubview:self.collectionView];
    [self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.bgView).offset(15);
        make.top.equalTo(self.bgView).offset(52);
        make.bottom.equalTo(self.bgView).offset(-20);
        make.right.equalTo(self.bgView).offset(-15);
    }];
    
}

- (UIView *)bgView {
    if (!_bgView) {
        _bgView = [[UIView alloc] init];
        _bgView.backgroundColor = XZWL_COLOR_FFFFFF;
        [_bgView cornerAngel:8];
    }
    return _bgView;
}

- (UILabel *)LLab {
    if (!_LLab) {
        _LLab = [[UILabel alloc]init];
        _LLab.font = FF_PFM_ICOME(16);
        _LLab.textColor = XZWL_COLOR_333333;
        _LLab.text = @"单选测试";
    }
    return _LLab;
}

- (UIImageView *)LImage {
    if (!_LImage) {
        _LImage = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"task_org"]];
    }
    return _LImage;
}

- (UICollectionView *)collectionView {
    
    if (!_collectionView) {
       //   核心代码 自定义布局LeftAlignFlowLayout
        LeftAlignFlowLayout *layout = [LeftAlignFlowLayout leftAlignLayoutWithDelegate:self];
        layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
        
        _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
        _collectionView.delegate = self;
        _collectionView.dataSource = self;
        _collectionView.showsHorizontalScrollIndicator = NO;
        _collectionView.showsVerticalScrollIndicator = NO;
        _collectionView.backgroundColor = [UIColor clearColor];
        
        [_collectionView registerClass:[ValueTypeCollectionViewCell class] forCellWithReuseIdentifier:ValueTypeCollectionViewCellReused];
        
        if (@available(iOS 11.0, *)) {
            _collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
        } else {
            self.viewController.automaticallyAdjustsScrollViewInsets = NO;
        }
        
    }
    return _collectionView;
}

#pragma mark - Event

#pragma mark - DataSource
#pragma mark -- UICollectionViewDataSource
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return 1;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return self.model.orgRelationCollection.count;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    ValueTypeCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:ValueTypeCollectionViewCellReused forIndexPath:indexPath];
    TaskValueModel *model = self.model.orgRelationCollection[indexPath.row];
    [cell configCell:model];
    return cell;
}

#pragma mark - Delegate
#pragma mark -- UICollectionViewDelegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
    //  单选功能
    TaskValueModel *model = self.model.orgRelationCollection[indexPath.row];
    //  选中再次点击取消选中
    if (model.selected) {
        model.selected = !model.selected;
    //  未选中点击选中,同时确保其它未选中
    } else {
        [self.model.orgRelationCollection enumerateObjectsUsingBlock:^(TaskValueModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            if (model.id == obj.id) {
                obj.selected  = YES;
            }else {
                obj.selected  = NO;
            }
        }];
    }
    [self.collectionView reloadData];
    
}
#pragma mark -- UICollectionViewDelegateFlowLayout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    TaskValueModel *model = self.model.orgRelationCollection[indexPath.row];
    NSString *str = ObjErrorCheck(model.value);
   // 核心代码 动态宽度计算
   //  LL_ScreenWidth -74 (文本的最大宽度)最大就是一行放置一个
    CGRect itemFrame = [str boundingRectWithSize:CGSizeMake(LL_ScreenWidth -74, 14) options: NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:14]} context:nil];
   //  item的宽度 文本宽度+左右间距合计24
    CGFloat width = itemFrame.size.width + 24;
    //  item的size
    return CGSizeMake(width, 32);
}

#pragma mark - Reload View
+ (CGFloat)getCellHeight:(iCCarrerCircleTaskDetailsNewModel *)model {
    CGFloat heightmp = 0;
    // 核心代码 高度计算
    if (model.orgRelationCollection.count) {
        // itemSizes 存放itemsize的集合
        NSMutableArray *itemSizes = [NSMutableArray array];
        for (TaskValueModel *modelt in model.orgRelationCollection)
        {
            CGRect itemFrame = [ObjErrorCheck(modelt.value) boundingRectWithSize:CGSizeMake(LL_ScreenWidth -74, 14) options: NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:14]} context:nil];
            
            CGFloat width = itemFrame.size.width + 24;
            NSValue *itemSize =[NSValue valueWithCGSize:CGSizeMake(width, 32)];
            [itemSizes addObject:itemSize];
        }
        
        NSInteger row = 1;
        CGFloat xOffset = 0 ;
       //  单个item高度
        CGFloat height = 32;
        for(int i = 0; i <itemSizes.count; i++)
        {
            CGFloat itemWidth = [itemSizes[i] CGSizeValue].width;
            //xOffset + 当前item的宽 <= 最大宽度(collectionView的宽度)
            if ((xOffset + itemWidth) <= (LL_ScreenWidth - 50))
            {
                xOffset += itemWidth + 10;
            }
            else
            {
                //换行
                xOffset  = itemWidth + 10;
                row++;
            }
        }
        //   collectionview高度: 行数 * 行高 + (行数 - 1)*行间距
        CGFloat maxHeight = row * height + (row - 1) * 10 ;
        //  总高度: collectionview高度 + 上下间距
        heightmp = maxHeight + 72;
        
    }
    return heightmp;
    
}

- (void)configCell:(iCCarrerCircleTaskDetailsNewModel *)model{
    self.model = model;
    self.collectionView.userInteractionEnabled = NO;
    if (self.model.orgRelationCollection.count) {
        TaskValueModel *modelt=self.model.orgRelationCollection[0];
        if (modelt.allowdClick) {
            self.collectionView.userInteractionEnabled = YES;
        }
        self.hidden = NO;
        [self.collectionView reloadData];
    } else {
        self.hidden = YES;
    }
    
}

@end

五、多选功能与单选相似,只是处理选中逻辑不同

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
    //    多选实现
    TaskValueModel *model = self.model.valueTypeCollection[indexPath.row];
    model.selected = !model.selected;
    [self.collectionView reloadData];
    
}

 

posted @ 2021-07-13 18:03  小菜看代码  阅读(3049)  评论(0编辑  收藏  举报