iOS UICollectionView實現動態標籤(單選、多選)
阿新 • • 發佈:2021-07-13
專案中我們經常會遇到標籤動態展示的問題,有時我們也需要實現單選或者多選的功能
<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]; }