基本实现了全选,删除等按钮点击功能
一. 首先,设置好基本的购物车界面,实现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