1. 程式人生 > 其它 >iOS UICollectionView實現動態標籤(單選、多選)

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];
    
}

今天我就簡述下使用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的寬 <= 最大寬度
            if ((xOffset + itemWidth) <= (LL_ScreenWidth - 74))
            {
                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];
    
}