前言
本文为iOS自定义视图封装《一劳永逸》系列的第三期,旨在提供封装思路,结果固然重要,但理解过程才最好。授人以鱼不如授人以渔。⚠️文章旨在帮助封装程度较低的朋友们,大神可无视勿喷。
历史文章链接列表:
正文
最近更新项目需求,需要封装一款多选弹窗视图,故将封装流程进行分享,效果图如下:
多选弹窗.png
对于弹窗视图,大体由两个视图进行搭建,一为蒙版视图(maskView)二为内容视图(contentView)。maskView通常设定一定的透明度允许用户交互点击取消弹窗操作,需要注意的是不要maskView做为contentView的子视图,因改变父视图透明度会影响子视图,因此maskView与contentView均需添加在弹窗视图self上,保证兄弟视图关系,这里不再赘述。对于contentView,因需求变动不大,本文直接采用collectionView作为内容视图,标题与决策按钮以组头组尾视图进行展示。
- (void)createUI {
[self addSubview:self.maskView];
[self addSubview:self.collectionView];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self removeFromSuperview];
}
接下来我们解决首要问题,即collectionView多选操作,既然tableView具有多选功能,那么collectionView为什么不呢?对于collectionView的多选功能的使用场景还是很多的,比如本例多选弹窗、自定义相册功能等。
接下来简单讲述下collectionView实现多选功能的实现,以下为部分核心代码。
⚠️collectionView开启多选功能,需将属性allowsMultipleSelection置为YES
_collectionView.allowsMultipleSelection = YES;
接下来我们需要准备两个数据源,分别存储全部选项与选中选项,命名为dataArr与selectedArr
@property (nonatomic, strong) NSMutableArray *dataArr;
@property (nonatomic, strong) NSMutableArray *selectedArr;
为了防止复用问题的出现,我们需要存储每个item的选中状态,这里选择创建数据模型进行实现。
#import <Foundation/Foundation.h>
@interface SureConditionModel : NSObject
@property (nonatomic, assign) BOOL isSelected;//是否选中 默认为NO
@property (nonatomic, copy) NSString *title;//选中item标题
@end
接下来对collectionView布局时,我们即可通过数据模型中提供的数据进行布局
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
SureConditionCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CONDITION" forIndexPath:indexPath];
SureConditionModel *model = _dataArr[indexPath.item];
[cell loadDataFromModel:model];
return cell;
}
@implementation SureConditionCollectionViewCell
- (void)loadDataFromModel:(SureConditionModel *)model {
_conditionLabel.text = model.title;
if (model.isSelected) {
_conditionLabel.backgroundColor = SureQuickSetRed;
_conditionLabel.textColor = [UIColor whiteColor];
_conditionLabel.font = [UIFont boldSystemFontOfSize:15.0];
} else {
_conditionLabel.backgroundColor = SureQuickSetWhite;
_conditionLabel.textColor = [UIColor darkGrayColor];
_conditionLabel.font = [UIFont systemFontOfSize:15.0];
}
}
在这里简单的更改了选中文字的背景颜色、字体颜色和字体,如有其他需求也可进行更改。
接下来我们实现collectionView选中与取消选中的代理方法即可,这里需要取得当前选中或取消选中的item对应的数据模型,并对其选中属性进行置反操作。然后进行添加或删除操作,选中与取消选中的最终结果都存储在selectedArr中。
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
[self selectedOrDeselectedItemAtIndexPath:indexPath];
}
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath {
[self selectedOrDeselectedItemAtIndexPath:indexPath];
}
- (void)selectedOrDeselectedItemAtIndexPath:(NSIndexPath*)indexPath {
SureConditionModel *model = _dataArr[indexPath.item];
model.isSelected = !model.isSelected;
[_collectionView reloadItemsAtIndexPaths:@[indexPath]];
if (model.isSelected) {
if (![_selectedArr containsObject:model]) {
[_selectedArr addObject:model];
}
} else {
if ([_selectedArr containsObject:model]) {
[_selectedArr removeObject:model];
};
}
}
执行上述操作,我们即可简单实现collectionView的多选功能。
接下来需要解决的问题为随选项个数动态调节collectionView高度的问题。即简单实现自适应内容高度。
因此我们需要获取collectionView的行数,但API中并没有给予。楼主通过数据源除列数获取行数,然后判断是否有余数进行++操作进行解决,如有更好的方式请评论指出,多谢。
NSInteger rows = window.dataArr.count / 2;
if (window.dataArr.count % 2 != 0) {
rows++;
}
既然UI上的操作基本考虑完成,接下里即需获取所选中选项并进行回调。因确认与取消按钮布局在collectionView的组尾上,因此我们需要回调传递所选选项,使用Block、代理等均可。
- (UICollectionReusableView*)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
SureHeaderCollectionReusableView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"HEADER" forIndexPath:indexPath];
headerView.titleLabel.text = _title;
return headerView;
} else if ([kind isEqualToString:UICollectionElementKindSectionFooter]) {
SureFooterCollectionReusableView *footerView = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"FOOTER" forIndexPath:indexPath];
__weak typeof(self) weakself = self;
[footerView setConfirmBlock:^{
__strong typeof(self) stongself = weakself;
if (stongself.selectedBlock) {
stongself.selectedBlock(stongself.selectedArr);
}
[stongself removeFromSuperview];
}];
[footerView setCancelBlock:^{
__strong typeof(self) stongself = weakself;
[stongself removeFromSuperview];
}];
return footerView;
} else {
return [[UICollectionReusableView alloc]init];
}
}
最后即为最重要的外漏方法问题,前几篇文章提过,为了降低代码耦合度,尽量外漏简易的方法,使其他开发人员可快速使用。并且在对象方法与类方法之间通常选择类方法进行实现。
考虑本文需求,既然为自定义View,因此数据获取一定不要放在View中,因此我们需要传入所有可选择数据,对于可选择数据可能具有默认选项,我们也可进行提供,最后最重要的为选中结果,我们需要将用户所选中的信息进行回调传递。
最终的结果如下,部分代码已省略,可前往demo查看。
+ (void)showWindowWithTitle:(NSString*)title
selectedConditions:(NSArray*)conditions
defaultSelectedConditions:(NSArray*)selectedConditions
selectedBlock:(void(^)(NSArray *selectedArr))block;
+ (void)showWindowWithTitle:(NSString*)title
selectedConditions:(NSArray*)conditions
defaultSelectedConditions:(NSArray*)selectedConditions
selectedBlock:(void(^)(NSArray *selectedArr))block{
SureMultipleSelectedWindow *window = [[SureMultipleSelectedWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
[[UIApplication sharedApplication].keyWindow addSubview:window];
//数据接收处理
window.selectedBlock = block;
//显示调节
}
调用方式如下
[SureMultipleSelectedWindow showWindowWithTitle:@"多选弹窗"
selectedConditions:@[@"我",@"是",@"卖报的",@"小画家",@"Sure"] defaultSelectedIndex:3
selectedBlock:^(NSArray *selectedArr) {
NSLog(@"%@",selectedArr);
}];
至此多选弹窗即封装完毕。demo已上传GitHub,喜欢的可以给个Star🙏。
一劳永逸,iOS多选弹窗封装流程🔗
网友评论
一个小建议,考虑低版本屏幕旋转的时候处理,具体可以参考mb源码