刚做完一个淘宝的购物车,按着淘宝做的,换了个产品经理,人家喜欢JD的购物车,一句话,咱换个风格,好心
酸有没有,天天刷存在感,只有我们苦逼了,那么既然需求来了,就要按着大爷的要求改了,为了纪念下,咱写个
Demo给大家分享下。
我擦,我一看代码,我还是用AutoLayout做的,主界面代码都能快接近800了,全加起来想想有点多啊,这简直是用
生命在写Demo啊,该有的效果全有了,各位请看图
再来一组
简单分析下功能
1.给UIKit控件增加Badge的扩展(这个扩展需要的去代码里面抠出来)
2.加入购物车的时候凹陷效果(这个效果我单独开了个博客分析的)---> 点击打开链接
3.购物车纯AutoLayout实现双层Cell高度自适应(这个有点叼,没用习惯的估计能看瞎)
4.购物车动态计算价格,多选或单选删除,编辑切换Cell,简单富文本等逻辑 (基础逻辑展示)
5.给购物车底部增加相关商品推荐 (主要是给TableFooterView加了CollectionView)
6.一个展示图片的组件 (JTSImageViewController)
7.为了不让看的人无聊,选了几个妹子
简单的看下代码
1.进入购物车之前凹陷效果模拟添加购物车
- (void)show:(UITapGestureRecognizer *)tap { if (!self.chooseVC){ self.chooseVC = [[ChooseGoodsPropertyViewController alloc] init]; } self.chooseVC.enterType = FirstEnterType; __weak typeof(self)weakSelf = self; self.chooseVC.block = ^{ NSLog(@"点击回调去购物车"); // 下面一定要移除,不然你的控制器结构就乱了,基本逻辑层级我们已经写在上面了,这个效果其实是addChildVC来的,最后的展示是在Window上的,一定要移除 [weakSelf.chooseVC.view removeFromSuperview]; [weakSelf.chooseVC removeFromParentViewController]; weakSelf.chooseVC.view = nil; weakSelf.chooseVC = nil; MKJShoppingCartViewController *shop = [MKJShoppingCartViewController new]; [weakSelf.navigationController pushViewController:shop animated:YES]; }; self.chooseVC.price = 256.0f; [self.navigationController presentSemiViewController:self.chooseVC withOptions:@{ KNSemiModalOptionKeys.pushParentBack : @(YES), KNSemiModalOptionKeys.animationDuration : @(0.6), KNSemiModalOptionKeys.shadowOpacity : @(0.3), KNSemiModalOptionKeys.backgroundView : [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"background_01"]] }]; }
这种效果我已经单独写出来了,需要的朋友可以点击打开链接
需要注意到的是他的层级关系,那么如果你像我一样要Push到下个界面的时候,务必把你的控制器和控制器的上
View remove掉,最好直接把指针置nil,类似于上面的代码这样操作,不然你的界面就飞了
2.来看看我吊炸天的AutoLayout布局双层Cell
先看看树形结构
contentView
NormalView(包含了正常状态下的控件)
EditBackView(包含了编辑状态下的控件)
chooseButton
ProductImageView
需要看详细布局的可以去下载Demo,感觉这种自适应的东西有点难讲,我自己都懵逼了
这里面有20来个控件,全约束好了,再配合FD一句代码实现高度自适应而且能编辑非编
辑切换,这TM省了几百行代码有木有啊......爽啊
// 高度计算 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { BuyerInfo *buyer = self.buyerLists[indexPath.section]; if (buyer.buyerIsEditing) { return 100; } else { CGFloat actualHeight = [tableView fd_heightForCellWithIdentifier:shoppongID cacheByIndexPath:indexPath configuration:^(ShoppingCartCell *cell) { [self configCell:cell indexPath:indexPath]; }]; return actualHeight >= 100 ? actualHeight : 100; } }
再设置一套代理,那么这个Cell基本交互就解决了
@protocol ShoppingCartCellDelegate <NSObject> // 点击单个商品选择按钮回调 - (void)productSelected:(ShoppingCartCell *)cell isSelected:(BOOL)choosed; // 点击buyer选择按钮回调 - (void)buyerSelected:(NSInteger)sectionIndex; // 点击buyer编辑回调按钮 - (void)buyerEditingSelected:(NSInteger)sectionIdx; // 点击垃圾桶删除 - (void)productGarbageClick:(ShoppingCartCell *)cell; // 点击编辑规格按钮下拉回调 - (void)clickEditingDetailInfo:(ShoppingCartCell *)cell; // 商品的增加或者减少回调 - (void)plusOrMinusCount:(ShoppingCartCell *)cell tag:(NSInteger)tag; // 点击图片回调到主页显示 - (void)clickProductIMG:(ShoppingCartCell *)cell; @end @interface ShoppingCartCell : UITableViewCell @property (nonatomic,assign) id<ShoppingCartCellDelegate>delegate;
3.简单介绍下购物车里面的交互功能
这里功能的思路基本是一致的,我们给商品model和buyerModel分别添加BOOL字段来
进行开或者关,然后在加载cell的时候做进一步判断
第一:例如计算总价(只需要更改商品的BOOL字段)
#pragma mark - 计算选出商品的总价 - (CGFloat)countTotalPrice { CGFloat totalPrice = 0.0; for (BuyerInfo *buyer in self.buyerLists) { if (buyer.buyerIsChoosed) { for (ProductInfo *product in buyer.prod_list) { totalPrice += product.order_price * product.count; } }else{ for (ProductInfo *product in buyer.prod_list) { if (product.productIsChoosed) { totalPrice += product.order_price * product.count; } } } } return totalPrice; }
第二:例如多选删除操作
这里需要注意的是,当删除批量的时候,一定不能再for循环里面一个一个删除,我们需要用一个临时数组抱起来,然
后再统一删除,不然会越界崩掉
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { // 单个删除 if (alertView.tag == 101) { if (buttonIndex == 1) { NSIndexPath *indexpath = [self.tableView indexPathForCell:self.tempCellArray.firstObject]; BuyerInfo *buyer = self.buyerLists[indexpath.section]; ProductInfo *product = buyer.prod_list[indexpath.row]; if (buyer.prod_list.count == 1) { [self.buyerLists removeObject:buyer]; } else { [buyer.prod_list removeObject:product]; } // 这里删除之后操作涉及到太多东西了,需要 [self updateInfomation]; } } else if (alertView.tag == 102) // 多个或者单个 { if (buttonIndex == 1) { // 买手数组 NSMutableArray *buyerTempArr = [[NSMutableArray alloc] init]; for (BuyerInfo *buyer in self.buyerLists) { if (buyer.buyerIsChoosed) { // 如果买手都被选择了,那么直接删除买手 [buyerTempArr addObject:buyer]; } else { // 商品数组 NSMutableArray *productTempArr = [[NSMutableArray alloc] init]; for (ProductInfo *product in buyer.prod_list) { if (product.productIsChoosed) { // 这里注意,批量删除商品别一次删除一个,需要放到一个容器里面一次性删除 [productTempArr addObject:product]; } } if (![[MKJRequestHelper shareRequestHelper] isEmptyArray:productTempArr]) { [buyer.prod_list removeObjectsInArray:productTempArr]; } } } [self.buyerLists removeObjectsInArray:buyerTempArr]; [self updateInfomation]; } } }
第三:就是要注意的事项,当你操作删除,选中,增加,减少的操作,都需要一并更新
价格 数量等UI
#pragma mark - 删除之后一些列更新操作 - (void)updateInfomation { // 会影响到对应的买手选择 for (BuyerInfo *buyer in self.buyerLists) { NSInteger count = 0; for (ProductInfo *product in buyer.prod_list) { if (product.productIsChoosed) { count ++; } } if (count == buyer.prod_list.count) { buyer.buyerIsChoosed = YES; } } // 再次影响到全部选择按钮 self.allSelectedButton.selected = [self isAllProcductChoosed]; // 总价选择 self.totalPriceLabel.text = [NSString stringWithFormat:@"合计¥%.2f",[self countTotalPrice]]; // 结算UI [self.accountButton setTitle:[NSString stringWithFormat:@"结算(%ld)",[self countTotalSelectedNumber]] forState:UIControlStateNormal]; // 刷新数据 [self.tableView reloadData]; // 如果删除干净了 if ([[MKJRequestHelper shareRequestHelper] isEmptyArray:self.buyerLists]) { [self clickAllEdit:self.rightButton]; self.rightButton.enabled = NO; } }
4.给底部FooterView增加一个CollectionView,来应对各种需要增加的需求
// 请求相关商品数据 [[MKJRequestHelper shareRequestHelper] requestMoreRecommandInfo:^(id obj, NSError *err) { [weakSelf.relatedProducts removeAllObjects]; weakSelf.relatedProducts = [[NSMutableArray alloc] initWithArray:(NSArray *)obj]; // 刷新数据 [weakSelf.collectionView reloadData]; // 根据刷新的数据,来获取CollectionViewlayout对应布局contenSize的高度和宽度(我们需要高度) weakSelf.underFooterView.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, weakSelf.collectionView.collectionViewLayout.collectionViewContentSize.height); // 现在再给FooterView进行UI赋值,避免出现不顺畅的显示 weakSelf.tableView.tableFooterView = weakSelf.underFooterView; }];
5.图片的一个组件(JTSImageViewController)基本用法展示
#pragma mark - 点击图片展示Show - (void)clickProductIMG:(ShoppingCartCell *)cell { NSIndexPath *indexpath = [self.tableView indexPathForCell:cell]; BuyerInfo *buyer = self.buyerLists[indexpath.section]; ProductInfo *product = buyer.prod_list[indexpath.row]; JTSImageInfo *imageInfo = [[JTSImageInfo alloc] init]; NSString *imageURLStr = product.image; imageInfo.imageURL = [NSURL URLWithString:imageURLStr]; JTSImageViewController *imageViewer = [[JTSImageViewController alloc] initWithImageInfo:imageInfo mode:JTSImageViewControllerMode_Image backgroundStyle:JTSImageViewControllerBackgroundOption_Scaled]; [imageViewer showFromViewController:self transition:JTSImageViewControllerTransition_FromOffscreen]; }
主要的代码就这么点,关键还是些逻辑的判断,最后检查下内存泄漏问题
- (void)dealloc
{
NSLog(@"%s____dealloc",object_getClassName(self));
}
打印了就OK了
一个简单的Demo就这么写完了,项目中就需要和服务器交互了,这里是本地的数据,各
位觉得需要可以下载Demo看看
Demo地址:点击打开链接
其实写这个需求之前我也去网上搜索了很多相关的购物车Demo,简直不能看啊,处理很
简单,UI也很简单,根本满足不了我,最后还是自己写了一个那么详细的留作纪念,可
能还存在很多问题,毕竟只是个Deme,抛砖引玉,各位觉得不错的可以下载研究下,觉
得有帮到您的记得点个赞哦~~~~~~