一劳永逸,iOS多选弹窗封装流程

作者: 卖报的小画家Sure | 来源:发表于2016-12-30 14:46 被阅读4068次
    前言

    本文为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多选弹窗封装流程🔗

    相关文章

      网友评论

      • 半边枫叶:系统的alertView应该是创了了一个单独的window吧,如果直接将封装的视图放到当前的keywinodow上,如果是有键盘的情况,还需要单独处理一下
      • BierLee:可以通过重写flowlayout来实现气泡式的布局,这样界面更优雅
      • sjwu:为什么maskview不是设置了rootviewcontroller的window 呢?能说下这两种方式的区别吗?
        卖报的小画家Sure:maskView与contentView是一个整体,都是加载到当前视图self上,最后self添加到window上。如果maskView设置为window,那视图间就分离了且不便于维护,封装性不好。
      • 难却却:我的大神
        卖报的小画家Sure:并不是大神,囧
      • zerojian:看到文章末尾调用方式是使用类方法,也就是说并没有让视图控制器引用这个 popview,那点击的 block 回调 是怎么保证可以执行的?
        卖报的小画家Sure:@zerojian 全局属性,看看demo兄弟
      • SkySongK:这个还可以优化吧,选项很多的时候应该让选择框有最大高度,然后让collectionView滚动,当选择项很多的时候你的取消和确定按钮就隐藏了,体验不好,应该固定在最下面,这样就完美了
        卖报的小画家Sure:demo中已经做上限限制,只是文中没有说明。contentView使用collectionView完成,因需求改动小所以没有设置固定效果。
      • 63fd6fe325e2:我是您大弟子,markmark
        卖报的小画家Sure:@tishka 哈哈:smile:
      • Frey丶:简单粗暴.mark一下.我曾经也是您的小弟子一枚.乌拉
      • 春泥Fu:给力d=(´▽`)=b
      • eb1190e47abf:mark稍后看
      • 雨天多久就:小小的疑问,父视图的透明度会影响子视图吗?用colorwithcompent这个方法设置透明度应该是没有问题的!
        一个小建议,考虑低版本屏幕旋转的时候处理,具体可以参考mb源码
        搬码小能手:@卖报的小画家Sure 兄弟视图简单粗暴,xib里有个Opacity透明度不影响子视图。
        卖报的小画家Sure:@雨天多久就 另外多谢建议,代码会持续更新维护下去 :smile:
        卖报的小画家Sure:@雨天多久就 嗯,colorwithcompent这个方法确实可以实现,但其归根到底是更改背景颜色,是获取当前颜色改变透明度之后的一个颜色作为backgroundColor,曾试过将maskView置UIImageView类型该方法会无效,因此实现的时候防坑还是使用的兄弟视图关系 :smile:
      • 清無:实现思路是可以的,你还得定制一些动画才好,还有输入框的、自定义view等适应多场景
        卖报的小画家Sure:@菲拉兔 嗯啊,目前只添加了展示动画,代码会持续更新的 :smile:
      • plu:卖报的小画家--刘?是么?
        plu:@卖报的小画家Sure 😁在看简书的最近文章,竟然这么巧
        卖报的小画家Sure:@plu 我应该说是还是是呢? :smile:
      • me007:collectionView的上限要做个规定,,不然太长了。。。
        卖报的小画家Sure:@不要感冒y永j 嗯啊,有上限限制。文中没有写,demo中已经考虑到了 :smile:

      本文标题:一劳永逸,iOS多选弹窗封装流程

      本文链接:https://www.haomeiwen.com/subject/igkjvttx.html