仿购物车的实现

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


一.  首先,设置好基本的购物车界面,实现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

原文地址:https://www.cnblogs.com/qhlbk/p/5236862.html