zoukankan      html  css  js  c++  java
  • 仿购物车的实现

    基本实现了全选,删除等按钮点击功能


    一.  首先,设置好基本的购物车界面,实现tableView的相关的 数据源 和 代理 方法
    在QHLShoppingCarController.m中写一个类扩展,定义下列属性:

    /** 模型数组 */
    @property (nonatomic, strong) NSMutableArray *shoppingCar;
    /** 底部结算view */
    @property (nonatomic, weak) QHLSettleMentView *settleMentView;
    /** 底部隐藏的按钮view */
    @property (nonatomic, weak) QHLHiddenView *hiddenView;
    /** 购物车界面状态  */
    @property (nonatomic, assign) QHLViewState state;
    /** 用来保存按钮选中时候的按钮字典数组 */
    @property (nonatomic, strong) NSMutableArray *btnsArray;
    /**  存储编辑状态下选中时对应的沙盒中的按钮字典数组 */
    @property (nonatomic, strong) NSMutableArray *documentArray;
    /** 存储在沙盒中的数据数组 */
    @property (nonatomic, strong) NSMutableArray *tempArray;
    /** 选中商品数量 */
    @property (nonatomic, assign) NSInteger count;
    /** 选中商品金额 */
    @property (nonatomic, assign) NSInteger money;

    在懒加载中,tempArray是用来保存存储在应用沙盒中的字典数组

    - (NSMutableArray *)tempArray {
        if (!_tempArray) {
            
            _tempArray = [NSKeyedUnarchiver unarchiveObjectWithFile:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"goods.archive"]];
            
        }
        return _tempArray;
    }

    抽取的方法 : 根据不同的self.state状态来设置不同的底部view的全选按钮的选中状态

    #pragma mark - 根据不同的选中状态来设置底部view的全选按钮的选中状态
    - (void)setButtonSelectState:(BOOL)selected {
        if (self.state == QHLViewStateNormal) { //根据状态来设置不同的全选按钮的选中
            self.settleMentView.btnSelected = selected;
        } else {
            self.hiddenView.allSelBtnSelected = selected;
        }
    }

    抽取的方法 : 根据不同的选中状态来对传入的数组做不同的操作

    #pragma mark - 根据不同的选中状态来对数组进行操作
    - (void)handleObjectInArrays:(id)btnsObject documentsObject:(id)documentsObject selectedState:(BOOL)selected{
        
        if (selected) { //选中时
            [self.documentArray addObject:documentsObject];
            [self.btnsArray addObject:btnsObject];
        } else { //取消选中时
            [self.documentArray removeObject:documentsObject];
            [self.btnsArray removeObject:btnsObject];
        }
    }

    二.  在自定义的cell中,cell中的选中button的点击事件通过代理放在控制器中去处理:

    在QHLShoppingCarController.m中实现cell的代理方法

    - (void)cell:(QHLTableViewCell *)cell selBtnDidClickToChangeAllSelBtn:(BOOL)selBtnSelectState andIndexPath:(NSIndexPath *)indexPath{
        BOOL selected = !selBtnSelectState;
        
        //获取模型
        QHLShop *shop = self.shoppingCar[indexPath.section];
        QHLGoods *good = shop.goods[indexPath.row];
        
        
        //设置按钮所在的cell的按钮的选中状态
        good.selected = selected;
        
        if (self.state == QHLViewStateNormal) { //判断当前view的state
            
            if (selected) {
                self.count ++;
                
                //添加金额
                self.money += [good.price integerValue];
                
            }else {
                self.count --;
                
                //减去金额
                self.money -= [good.price integerValue];
            }
            
            self.settleMentView.count = self.count;
            self.settleMentView.money = self.money;
            
        } else { //edit 状态
            
            //获取沙盒数组中的模型
            QHLShop *shops = self.tempArray[indexPath.section];
            QHLGoods *goods = shops.goods[indexPath.row];
            
            [self handleObjectInArrays:good documentsObject:goods selectedState:selected];
            
        }
        
        
        
        //设置按钮cell所在的表头视图的按钮状态
        for (QHLGoods *goods in shop.goods) {
            
            if (!goods.selected) {
                
                shop.selected = NO;
                [self.tableView reloadData];
                
                if (self.state == QHLViewStateNormal) {
                    
                    self.settleMentView.btnSelected = NO; //全选按钮设置为未选中状态
                    
                } else {
                    
                    //获取沙盒数组中的模型
                    QHLShop *shops = self.tempArray[indexPath.section];
                    
                    [self.btnsArray removeObject:shop];
                    [self.documentArray removeObject:shops];
                    
                    self.hiddenView.allSelBtnSelected = NO;  //全选按钮设置为未选中状态
                }
                return;
            }
        }
        //能执行到此处,就说明该组cell的按钮 都是选中状态
        shop.selected = YES;
        if (self.state == QHLViewStateEdited) {
            //获取沙盒数组中的模型
            QHLShop *shops = self.tempArray[indexPath.section];
            
            [self.btnsArray addObject:shop];
            [self.documentArray addObject:shops];
        }
        
        [self.tableView reloadData];
        
        //根据表透视图的按钮状态设置全选按钮的状态
        for (QHLShop *shop in self.shoppingCar) {
            if (!shop.selected) {
                
                [self setButtonSelectState:NO];
                
                return;
            }
        }
        [self setButtonSelectState:YES];
    }

    在该自定义cell的button点击的代理方法中:
    1. 当点击cell中的btn的时候,先判断当前购物车的state是 QHLViewStateNormal 还是 QHLViewStateEdited!!

    当state是 QHLViewStateNormal 的时候:
        对self.count 和self.money 作 增加 或者 减少 的操作.

    当state是 QHLViewStateEdited 的时候:
        调用方法 : [self handleObjectInArrays:good documentsObject:goods selectedState:selected]
        1> 当selected == YES时,把当前点击的cell相对应的self.shoppingCar中的模型对象(good) 保存到self.btnsArray中 或者 从self.btnsArray中移除
        2> 当selected == NO时,把当前点击的cell相对应的self.tempArray中的模型对象(goods)  保存到self.documentArray中 或者 从self.documentArray中移除

    2. 之后遍历该cell所在的组的模型数据数组:
    如果cell所在组的模型数组中的数据有 good.selected是 NO 的,就设置该组组头视图中的 btn 状态为未选中并直接 return; 退出该方法;如果所有good.selected 都是YES,那么设置该组组头视图中的 btn 的状态为选中,并刷新数据.

        2.1> 在该组cell所在的模型数据数组中有 good.selected 是NO 的情况时:
    当state是 QHLViewStateNormal 的时候:
        设置self.settleMentView.btnSelected = NO,  设置后会在相对应的setter方法中来设置对应的全选按钮状态为未选中.

    当state是 QHLViewStateEdited 的时候,在return之前执行下面操作:
        1. 设置self.hiddenView.allSelBtnSelected = NO,  设置后悔在相对应的setter方法中来设置相应的全选按钮的状态为未选中.
        2. 把当前点击的cell在self.shoppingCar中所对应的组头的模型对象(shop) 从self.btnsArray中移除
        3. 把当前点击的cell在self.tempArray中所对应的组头的模型对象(shops) 从self.documentArray中移除

        2.2> 在该组cell所在的模型数据中所有 good.selected 都是 YES 的情况时:
            1. 设置该组cell所在组相对应的组头模型中的selected为YES,并刷新tableView.
            2. 当state是 QHLViewStateEdited 的时候:
                把该组头视图在 self.shoppingCar中所对应的模型对象(shop)  保存到 self.btnsArray中.
                把该组头视图在 self.tempArray中所对应的模型对象(shops)  保存到 self.documentArray中.


        2.3> 之后在遍历整个self.shoppingCar中的每个QHLShop对象,当存在有shop对象的selected 是NO的情况时,根据不同的state状态 设置不同的view的选中属性为NO,并且直接 return 退出方法;当所有shop对象的selected 都是YES时,根据不同是state状态 设置不同的view的选中属性为YES.

    三.  在自定义的组头视图中,视图中的选中button的点击事件通过代理放在控制器中去处理:
    在QHLShoppingCarController.m中实现headerView的代理方法

    #pragma mark - headerView的代理方法
    - (void)headerView:(QHLHeaderView *)headerView selBtnDidClickToChangeAllSelBtn:(BOOL)selBtnSelectState andSection:(NSInteger)section {
        
        BOOL selected = !selBtnSelectState;
        
        //获取对应的模型
        QHLShop *shop = self.shoppingCar[section];
        
        if (self.state == QHLViewStateNormal) {
            
            if (selected) {
                for (QHLGoods *good in shop.goods) {
                    //判断表头所在的cell组中  cell中的按钮 是否选中,当不选中的情况下 执行下面代码
                    if (!good.selected) {
                        self.count ++;
                        //添加金额
                        self.money += [good.price integerValue];
                    }
                }
            } else { //这边不用做判断,表头视图中的cell中的按钮 都是选中状态
                for (QHLGoods *good in shop.goods) {
                    self.count --;
                    //添加金额
                    self.money -= [good.price integerValue];
                }
            }
            
            self.settleMentView.count = self.count;
            self.settleMentView.money = self.money;
            
        } else {  //edit 状态
            QHLShop *shops = self.tempArray[section];
            
            if (selected) { //组头视图中的button选中时
                [self.documentArray addObject:shops];
                [self.btnsArray addObject:shop];
            } else { // 组头视图中的button取消选中时
                [self.documentArray removeObject:shops];
                [self.btnsArray removeObject:shop];
            }
            
            NSInteger count = shop.goods.count;
            for (int i = 0; i < count; i++) {
                
                if (selected) {
                    [self.documentArray addObject:shops.goods[i]];
                    [self.btnsArray addObject:shop.goods[i]];
                } else {
                    [self.documentArray removeObject:shops.goods[i]];
                    [self.btnsArray removeObject:shop.goods[i]];
                }
            }
        }
        
        //设置表头view的按钮状态
        shop.selected = selected;
        
        //设置表头所在组cell的按钮状态
        for (QHLGoods *good in shop.goods) {
            good.selected = selected;
        }
        [self.tableView reloadData];
        
        
        //设置全选按钮的选中状态
        for (QHLShop *shop in self.shoppingCar) {
            if (!shop.selected) {
                
                if (self.state == QHLViewStateNormal) { //根据状态来设置不同的全选按钮的选中
                    self.settleMentView.btnSelected = NO;
                } else {
                    self.hiddenView.allSelBtnSelected = NO;
                }
                
                return;
            }
        }
        if (self.state == QHLViewStateNormal) { //根据状态来设置不同的全选按钮的选中
            self.settleMentView.btnSelected = YES;
        } else {
            self.hiddenView.allSelBtnSelected = YES;
        }
    }

    在该自定义headerView的删除button点击的代理方法中:

    1. 当点击cell中的btn的时候,先判断当前购物车的state是 QHLViewStateNormal 还是 QHLViewStateEdited!!

    当 当前的state 是QHLViewStateNormal的时候:
        1> 如果headerView中的button状态为选中时候,遍历当前headerView所在组相对应的shop.goods模型数组:
            1> 遍历时,当模型数组中的good.selected == NO 时, 对 self.count 和 self.money 自加;如果good.selected == YES 时,不做处理

        2> 如果headerView中的button状态为未选中时候,遍历当前headerView所在组相对应的shop.goods模型数组:
            1> 遍历时,对self.count 和 self.money 做自减操作

    当 当前的state 是QHLViewStateEdited的时候:
        1> 调用方法 : [self handleObjectInArrays:shop documentsObject:shops selectedState:selected] 根据 headerView中的button选中状态selected来添加或者移除self.btnsArray 和 self.documentArray 中 对应的QHLShop 模型对象

        2> 根据当前headerView所在的组的cell个数 做循环, 在循环中调用方法 : [self handleObjectInArrays:shop.goods[i] documentsObject:shops.goods[i] selectedState:selected]  根据 headerView中的button选中状态selected来添加或者移除self.btnsArray 和 self.documentArray 中 对应的QHLGoods 模型对象

    2. 对 shop.goods 模型数组做循环遍历:
        根据当前shop.selected 的 bool 值 来设置 good.selected 的值(即 good.selected == shop.selected)

    3. 对 self.shoppingCar 模型数组做循环遍历:
        1> 当该数组中 至少有一个shop.selected == NO的时候,直接return 退出该方法;
           在return之前 调用方法 : [self setButtonSelectState:NO] 根据不同的state状态来设置不同的view中的全选按钮的隐藏

        2> 当所有的shop.selected == YES 的时候,遍历结束之后,调用方法 : [self setButtonSelectState:YES] 根据不同的state状态来设置不同的view中的全选按钮的隐藏

    四.  在该自定义headerView的全选按钮的点击事件的代理方法中:
    遍历self.shoppingCar模型数组,根据全选按钮的不同的选中状态,调用 handleObjectInArrays: documentsObject: selectedState: 方法来对self.documentArray 和 self.btnsArray 数组 做不同的操作

    五.  在右上角编辑按钮的点击事件中:
    - (void)editBtnDidClick:(QHLButton *)editBtn方法中

    设置底部2个view的隐藏显示切换
    改变编辑按钮的选中状态

    1> 当编辑按钮选中时:
        1. 改变self.state  变为 编辑状态
        2. 保存self.shoppingCar模型数组,归档写入到应用沙盒中
        3. 遍历self.shoppingCar 模型数组,是其中所有的模型的selected属性为 NO,最后刷新tableView

    2> 当编辑按钮取消选中时:
        1. 改变self.state 变为 正常状态
        2. 设置隐藏view的全选按钮的选中属性为NO
        3. 把self.tempArray(存储在沙盒中的模型数据数组) 赋值给 self.shoppingCar,然后刷新tableView
        4. 把self.btnsArray self.documentArray self.tempArray 置为 nil
        5. 判断self.shoppingCar中元素的个数,如果个数为0 设置结算view中的全选按钮的选中状态为NO,并return 退出方法;如果self.shoppingCar.count不为0的话执行下面操作
        6. 先把 self.count 和 self.money 的值 等于0!!!然后遍历self.shoppingCar模型数组,根据QHLGoods模型对象good的selected 是否等于 YES 来使self.count 和 self.money 执行自加操作
        7. 遍历self.shoppingCar中的shop.goods模型数组:
            1> 当数组中有QHLGoods模型对象的selected的值是NO的话,break跳出本次循环.在break之前设置该对象所在的shop.selected为NO,和结算view的全选按钮的状态为NO
            2> 当shop.goods中的good.selected都是YES的时候,shop.selected 设置为YES.
        8.遍历self.shoppingCar模型数组:
            1> 当shop.selected 的值 有为NO时,设置结算view中的全选按钮的状态为未选中,并且return退出方法.
            2> 当所有shop.selected的值 都是YES时,设置结算view中的全选按钮的状态为选中.

    六.  抽取的用来遍历数组删除数据的方法

    /**
     *  @param dataArray  dataArray 保存要删除的对象的数组
     *  @param modelArray modelArray 模型数组 要从该数组中删除数据
     */
    #pragma mark - 遍历删除模型数组dataArray中的元素在模型数组modelArray中对应的object
    - (void)enumerateObjectsUsForinWithDataArray:(NSMutableArray *)dataArray modelArray:(NSMutableArray *)modelArray {
        
        //遍历删除 对应的good
        for (id obj in dataArray) {
            
            for (QHLShop *shops in modelArray) {
                
                for (QHLGoods *goods in shops.goods) {
                    
                    if ([goods isEqual:obj]) {
                        
                        [shops.goods removeObject:goods];
                        break;
                        
                    }
                }
            }
        }
        
        //遍历删除 对应的shops
        for (id obj in dataArray) {
            
            for (QHLShop *shops in modelArray) {
                
                if ([shops isEqual:obj]) {
                    
                    [modelArray removeObject:shops];
                    break;
                }
            }
        }
    }

    七.  编辑状态下,在删除按钮的点击事件中:

    #pragma mark - hiddenView delegate
    - (void)hiddenView:(QHLHiddenView *)hiddenView didClicHiddenViewBtn:(QHLHiddenViewButtonState)buttonType {
        if (buttonType == QHLHiddenViewButtonStateShared) {
            
        } else if (buttonType == QHLHiddenViewButtonStateAttention) {
            
        } else if (buttonType == QHLHiddenViewButtonStateDelete) {
            
            //修改self.shoppingCar
            [self enumerateObjectsUsForinWithDataArray:self.btnsArray modelArray:self.shoppingCar];
            
            [self.tableView reloadData];
            
            //修改保存沙盒数据的数值中对应的数据
            [self enumerateObjectsUsForinWithDataArray:self.documentArray modelArray:self.tempArray];
            
            if (!self.shoppingCar.count) {
                self.hiddenView.allSelBtnSelected = NO;
            }
        }
    }

    在按钮的删除方法中,先遍历删除QHLGoods对象模型,在遍历QHLShop对象模型,根据self.btnsArray 和 self.documentArray 中保存的元素 在 self.shoppingCar 和 self.tempArray 中 删除相对应的模型对象.遍历结束后,判断self.shoppingCar中是否还有元素,没有元素了的话设置全选按钮为未选中状态

    八.  在添加cell侧滑删除功能的方法中:

    #pragma mark - 设置cell侧滑按钮
    - (nullable NSArray<UITableViewRowAction *> *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath {
        
        QHLShop *shop = self.shoppingCar[indexPath.section];
        
        //删除
        UITableViewRowAction *deleteAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:@"删除" handler:^(UITableViewRowAction * _Nonnull action, NSIndexPath * _Nonnull indexPath) {
            //删除数据
            [shop.goods removeObjectAtIndex:indexPath.row];
            
            
            if (!shop.goods.count) {   //判断 shoppingCar数组中的shop对象的goods数组 是否为空  为空的话移除shop  不为空的话 移除good
                [self.shoppingCar removeObjectAtIndex:indexPath.section];
            }
            [tableView reloadData];
            
            
            if (self.state == QHLViewStateEdited) { //在编辑界面下删除cell时  同时删除沙盒中读取到的数组中对应的元素
                //根据indexPath获取到QHLShop对象
                QHLShop *shops = self.tempArray[indexPath.section];
                
                //移除该对象goods数组中的下标为indexPath.row的元素
                [shops.goods removeObject:shops.goods[indexPath.row]];
                
                if (!shops.goods.count) { //判断沙盒存储的数组中的shop对象的goods数组 是否为空  为空的话移除
                    [self.tempArray removeObject:shops];
                }
            }
            
            for (QHLGoods *good in shop.goods) {
                if (!good.selected) {
                    shop.selected = NO;
                    [self setButtonSelectState:NO];
                    [tableView reloadData];
                    return;
                }
            }
            shop.selected = YES;
            if (self.state == QHLViewStateEdited) {
                //根据indexPath获取到QHLShop对象
                QHLShop *shops = self.tempArray[indexPath.section];
                [self handleObjectInArrays:shop documentsObject:shops selectedState:YES];
            }
            [tableView reloadData];
            
            for (QHLShop *shop in self.shoppingCar) {
                if (!shop.selected) {
                    [self setButtonSelectState:NO];
                    return;
                }
            }
            [self setButtonSelectState:YES];
            
        }];
        
        return @[deleteAction];
    }

    在删除按钮的回调方法中:
        1> 当删除按钮被点击时,会先把对应的QHLGoods模型对象从shop.goods中删除掉,然后判断shop.goods中是否还有别的元素,没有的话删从self.shoppingCar中删除掉shop模型对象,然后刷新tableView.
        2> 然后判断当前self.state是否是编辑状态,是的话,根据indexPath在模型数组self.tempArray(存储在沙盒中的模型数组)中删除相对应的goods模型对象,然后判断该模型对象所在的shops.goods是否还有别的元素,没有了的话把shops模型对象从self.tempArray中移除掉.
        3> 遍历删除的cell所在的组模型数组shop.goods模型数组,当有good.selected == NO时,设置shop.selected = NO 并且根据不同的state状态 设置不同的底部view的全选按钮的选中状态,最后return结束回调方法
        4> 当遍历结束时,说明shop.goods中所有的good.selected 都是YES,所以设置shop.selected = YES,如果当前的state是在编辑状态下的时候,把对应的shop模型对象和shops模型对象保存到对应的数组中,并且刷新tableView.
        5> 遍历self.shoppingCar模型数组,如果有shop.selected = NO的话,调用[self setButtonSelectState:NO]方法 根据不同的strate来设置不同的全选按钮为未选中状态,并且退出回调方法;如果所有的shop.selected 都是YES的话,调用[self setButtonSelectState:NO]方法 根据不同的strate来设置不同的全选按钮为选中状态.

    通过这些主要的方法的实现以及tableView的 数据源 和 代理 方法的实现,差不多就可以实现购物车的基本功能了.

    demo已经上传到cocoachina那边了 连接:http://code.cocoachina.com/view/129721

  • 相关阅读:
    ExtJS小技巧
    Oracle 表的行数、表占用空间大小,列的非空行数、列占用空间大小 查询
    NPM 私服
    IDEA 不编译java以外的文件
    SQL 引号中的问号在PrepareStatement 中不被看作是占位符
    Chrome 浏览器自动填表呈现淡黄色解决
    批量删除Maven 仓库未下载成功.lastupdate 的文件
    Oracle 11g 监听很慢,由于监听日志文件太大引起的问题(Windows 下)
    Hibernate 自动更新表出错 建表或添加列,提示标识符无效
    Hibernate 自动更新表出错 More than one table found in namespace
  • 原文地址:https://www.cnblogs.com/qhlbk/p/5236862.html
Copyright © 2011-2022 走看看