有时候 TableView 我们可能只会需要他的动态性(数据驱动)而 Cell 重用可能会导致问题
面对下面的界面我们该如何去做 ??
界面描述:
- 这是一个关于商品的界面, 商品依据店家来分类, 所有如果使用 tableView我们可以设置 Session 为店家的个数
- 商店下的商品个数不确定, 可变, 所以我选择再次嵌套一层 TableView, 这个 TableView 就只显示商品
所以界面拆分如下:
界面拆分完成, 接下来应该梳理下数据的问题, 界面流程如下:
- 程序进入到该界面, 从前一个界面中传递的信息中获得所有商品的价格, 计算总价格, 推出整个页面底部的合计金额
- 主界面请求网络获得用户的地址, 用户账号下的礼品卡余额
- 每个商店根据自己的商店信息发起网络请求, 请求可以使用的优惠方式(红包, 打折卡)
- 请求获得每个商店的优惠, 更新 UI, 将不包含的优惠从界面中移除
- 当用户在商家下选择任意或取消一种优惠时, 商家底部的实付要做相应的更新, 同时底部的总价要做相应的更新
- 当选择好商家优惠, 用户再次选择余额或礼品卡同样样做相应的 UI 及数据更新, 防止总价小于优惠的价格导致总价出现负数的情况
- 项目后期拆分订单被取消, 替代的是商家的运费, 不同的商家要请求获得运费, 这个请求的运费也会影响商家下的实付金额, 界面下的合计金额
根据上面的流程, 我做如下的设计:
- 整个 ViewController 的数据由一个 PrimaryDataController 控制
- 每个商店的数据由一个 SubDataController 控制
- PrimaryDataController 有个数组包含 SubDataController, 有多少商店就有多少 SubDataController 对象
- 每个蓝色区域的 Cell 绑定一个 SubDataController(注意这是个埋坑点)
- 因为各个不同的店家的优惠要分别异步请求, 请求结束后要刷新界面, 我们还要监听店家优惠金额的改变, 监听变化有几种可选措施: (1). KVO, 通知 (2). 代理, (3). Block; 首先 KVO, 通知可以放弃, 因为他们是一对多的关系, 更改优惠方法发出一条通知, 而整个界面所有的蓝色 Cell 都是监听者, 这样无法确定哪一个应该发生相应的更改. 代理, Block 的回调触发都是一对一的, 这两种作为可选操作, 这里我选择的是 Block
- 因为第5步我选择的 Block 触发回调, 看第四步有坑, 这里的坑是指蓝色 Cell 指定相同的Identifier, 所以蓝色 Cell 是可以重用的, 如果一重用, SubDataController 的回调绑定关系就会产生问题, 这个地方是我掉的一个坑, 还有如果店家的优惠信息请求结束要相应的对优惠进行显示和隐藏, 就是要更新蓝色 Cell 的高度, 这样难免调用 TableView 的 reloadData, 如果一调用 reloadData, 界面就会重新赋值, 所以会面临一个问题, 有多个店家, 假设店家优惠请求结束是依次结束, 这样当用户下滑时, 第一个蓝色 Cell 进入重用池, 然后再从重用池中取出显示另一个店家商品信息, 这样第一个店家优惠信息请求结束实际是在这个显示这个店家商品信息刷新, 而且会一个接一个结束, 不停地 reloadData, 导致问题
具体从代码中了解如何解决:
首先创建 PrimaryDataController
#import
@interface WN_PrimaryDataController : NSObject
@property (nonatomic, strong) NSDictionary *shoppingCartsInfo;
/**
* 每个品牌 Section 的数据控制器
*/
@property (nonatomic, strong) NSArray *subDataControllers;
/**
* 确认订单中总价钱
*/
@property (nonatomic, strong) NSDecimalNumber *totalPrice;
/**
* 除去优惠方法的商品总价
*/
@property (nonatomic, strong, readonly) NSDecimalNumber *originalTotalPrice;
/**
* 优惠价格
*/
@property (nonatomic, strong) NSDecimalNumber *privilegePrice;
/**
* 更改总价后触发 View 中的回调
*/
@property (nonatomic, copy) void (^changeTotalPriceCallBack)(NSDecimalNumber *totalPrice);
/**
* 确认订单中商品总数
*/
@property (nonatomic, assign, readonly) NSUInteger productCount;
/**
* 用户输入的使用的礼品卡金额
*/
@property (nonatomic, copy) NSString *giftCardValue;
/**
* 总的礼品卡余额
*/
@property (nonatomic, copy, readonly) NSString *totalGiftCardValue;
/**
* 用户输入的使用的用户余额
*/
@property (nonatomic, copy) NSString *balanceValue;
/**
* 运费
*/
@property (nonatomic, strong) NSDecimalNumber *freight;
/**
* 总的用户余额
*/
@property (nonatomic, copy, readonly) NSString *totalUserBalanceValue;
@property (nonatomic, copy) void (^resertGiftValue)();
@property (nonatomic, copy) void (^resertBalanceValue)();
@property (nonatomic, copy) void (^resertGiftCardOrBalance)();
/**
* 某一个品牌下添加红包或者打折卡导致总优惠发送变化
*
* @param price 某个品牌选择红包或者打折卡产生的优惠金额
*/
- (void)addPrivilegePrice:(NSDecimalNumber *)price;
/**
* 某个品牌下移除红包或者打折卡导致总优发生变化
*
* @param price 某个品牌选择红包或者打折卡产生的优惠金额
*/
- (void)removePrivilegePrice:(NSDecimalNumber *)price;
@end
SubDataController:
#import
#import "WN_PrivilegeModel.h"
@class WN_PrimaryDataController;
@interface WN_SubDataController : NSObject
@property (nonatomic, weak) WN_PrimaryDataController *primaryDataController;
/**
* 该品牌下商品的总价
*/
@property (nonatomic, strong, readonly) NSDecimalNumber *totalPrice;
/**
* 原始价格, 用于计算打折卡折扣
*/
@property (nonatomic, strong) NSDecimalNumber *originalTotalPrice;
/**
* 优惠价格
*/
@property (nonatomic, strong, readonly) NSDecimalNumber *privilegePrice;
/**
* 更改总价后触发 View 中的回调
*/
@property (nonatomic, copy) void (^changeTotalPriceCallBack)(NSDecimalNumber *totalPrice);
/**
* 优惠更改后触发 View 中的回调
*/
@property (nonatomic, copy) void (^changePrivilegePriceCallBack)(NSDecimalNumber *privilegePrice);
@property (nonatomic, copy) void (^changeFreightPriceCallBack)(NSString *freightPrice);
/**
* 更改优惠后 PrimaryDataController 中的回调
*/
@property (nonatomic, copy) void (^changePrivilegePricePrimaryDataControllerCallBack)(NSDecimalNumber *privilegePrice);
/**
* 该品牌下每件商品的礼品卡使用比例
*/
@property (nonatomic, strong) NSMutableArray *giftCardUsageRanges;
/**
* 设置该品牌下所有的商品信息
*/
- (void)setCartItemInfo:(NSDictionary *)dic;
/**
* 该品牌下商品的总数
*/
@property (nonatomic, assign, readonly) NSInteger productCount;
/**
* 可用红包数组
*/
@property (nonatomic, strong, readonly) NSArray *redPackets;
/**
* 可用红包个数
*/
@property (nonatomic, assign, readonly) NSUInteger validityRedPacketsCount;
/**
* 红包模型
*/
@property (nonatomic, strong, readonly) WN_RedpacketModel *redpacketModel;
/**
* 可用打折卡数组
*/
@property (nonatomic, strong, readonly) NSArray *discountCards;
/**
* 可用打折卡个数
*/
@property (nonatomic, assign, readonly) NSUInteger validityDiscountCardCount;
/**
* 打折卡模型
*/
@property (nonatomic, strong, readonly) WN_DiscountCardModel *discountCardModel;
/**
* 发票信息
*/
@property (nonatomic, copy) NSDictionary *invoiceInfoDictionary;
/**
* 运费
*/
@property (nonatomic, copy, readonly) NSString *freight;
@property (nonatomic, copy, readonly) NSString *shoppingCardIds;
/**
* 第几个品牌
*/
@property (nonatomic, assign) NSUInteger index;
@property (nonatomic, copy) NSString *brandId;
@property (nonatomic, assign) BOOL loading;
@property (nonatomic, copy) void (^changeLoadPrivilegeStatusCallBack)(BOOL loading);
/**
* 根据品牌价值完红包打折卡后的回调
* @param callBack 根据品牌价值完红包打折卡后的回调
* redPackets 红包数组
* redPackCount 红包个数
* discounts 打折卡数组
* discountCardCount 打折卡个数
* redpacketModel 与本品牌关联的红包模型
* discountCardModel 与本品牌关联的打折卡模型
* freight 运费
*/
- (void)loadDataFinishedCallBack:(void (^)(NSArray *redPackets,
NSUInteger redPackCount,
NSArray *discounts,
NSUInteger discountCardCount,
WN_RedpacketModel *redpacketModel,
WN_DiscountCardModel *discountCardModel,
NSString *freight)) callBack;
@end
TableViewController 中:
- 根据数据创建 PrimaryDataController, SubDataController:
- (WN_PrimaryDataController *)primaryDataController {
if (!_primaryDataController) {
// 创建主数据控制器
_primaryDataController = [[WN_PrimaryDataController alloc] init];
NSMutableArray *subDataControllers = [NSMutableArray arrayWithCapacity:_brands.count];
__weak typeof(self)weakSelf = self;
__weak typeof(_primaryDataController)weakPrimayDataController = _primaryDataController;
// 根据每个店家的数据创建店家的数据控制器
for (NSInteger index = 0; index < _brands.count; index++) {
NSDictionary *obj = _brands[index];
WN_SubDataController *subDataController = [[WN_SubDataController alloc] init];
subDataController.index = index;
subDataController.brandId = obj[@"brandId"];
subDataController.primaryDataController = weakPrimayDataController;
[subDataController setCartItemInfo:obj];
[subDataControllers addObject:subDataController];
}
_primaryDataController.subDataControllers = subDataControllers;
[_primaryDataController setChangeTotalPriceCallBack:^(NSDecimalNumber *totalPrice) {
weakSelf.totalPriceLabel.text = [NSString stringWithFormat:@"%.2f",[totalPrice floatValue]];
}];
[_primaryDataController setetUserBalanceText:^(NSString *placeholder, NSString *giftCardValue, NSString *text, NSString *balanceValue) {
weakSelf.userBalanceView.balanceTextField.placeholder = text;
weakSelf.userBalanceView.giftCardTextField.placeholder = placeholder;
[weakSelf.userBalanceView setGiftcards:giftCardValue userBalance:balanceValue];
}];
_totalPriceLabel.text = [NSString stringWithFormat:@"%.1f",[_primaryDataController.totalPrice floatValue]];
_productCountLabel.text = [NSString stringWithFormat:@"共%zd 件商品",_primaryDataController.productCount];
}
return _primaryDataController;
}
-
蓝色 Cell
#import
@class WN_SubDataController, WN_PrimaryDataController; @interface WN_ProductInfoCell : UITableViewCell @property (nonatomic, strong, readonly) NSDictionary *info; // 对应一个数据控制器 @property (nonatomic, strong, readonly) WN_SubDataController *subDataController; @property (nonatomic, copy) void (^reloadView)(); /** * 设置品牌商品信息, 配置一个数据控制类 * * @param info 商品信息 * @param dataController 数据控制类 */ - (void)setBrandProductsInfo:(NSDictionary *)info brandProductDataController:(WN_SubDataController *)dataController; + (instancetype)cell; @end -
蓝色 Cell 绑定子数据控制器, 并绑定数据回调
- (void)setBrandProductsInfo:(NSDictionary *)info brandProductDataController:(WN_SubDataController *)subDataController { _info = info; _subDataController = subDataController; self.proDSDelegate.products = _info[@"shoppingCartItemdetail"]; self.productItemTableViewHeightConstraint.constant = self.proDSDelegate.products.count * ProductItemRowHeight; [UIView animateWithDuration:.3 animations:^{ [self.contentView performSelector:@selector(layoutIfNeeded) withObject:nil afterDelay:0.f inModes:@[NSDefaultRunLoopMode]]; }]; self.totalPriceLabel.text = [NSString stringWithFormat:@"%.2f",[_subDataController.totalPrice floatValue]]; __weak typeof(self)weakSelf = self; [_subDataController setChangeLoadPrivilegeStatusCallBack:^(BOOL loading) { // 运费获得回调 weakSelf.freightMaskView.hidden = loading ? NO: YES; }]; [_subDataController setChangeTotalPriceCallBack:^(NSDecimalNumber *totalPrice) { // 总价发生变化回调 weakSelf.totalPriceLabel.text = [NSString stringWithFormat:@"%.2f",[totalPrice floatValue]]; }]; [_subDataController setChangePrivilegePriceCallBack:^(NSDecimalNumber *privliegePrice) { // 优惠金额发送变化 weakSelf.privilegeLabel.text = [NSString stringWithFormat:@"优惠%.2f 元",[privliegePrice floatValue]]; }]; [_subDataController setChangeFreightPriceCallBack:^(NSString *freight) { [weakSelf.freightLabel setText:[NSString stringWithFormat:@"运费 %@元",freight]]; }]; [_subDataController loadDataFinishedCallBack:^(NSArray *redPackets, NSUInteger redPackCount, NSArray *discounts, NSUInteger discountCardCount, WN_RedpacketModel *redpacketModel, WN_DiscountCardModel *discountCardModel, NSString *freight) { __strong typeof(weakSelf)strongSelf = weakSelf; // 配置优惠 View(红包打折卡) [strongSelf.privilegeView setRedPackets:redPackets redpacketCount:redPackCount redpacketModel:redpacketModel discountCards:discounts discountCardCount:discountCardCount discountCardModel:discountCardModel]; [UIView animateWithDuration:.3 animations:^{ [weakSelf.contentView performSelector:@selector(layoutIfNeeded) withObject:nil afterDelay:0.f inModes:@[NSDefaultRunLoopMode]]; }]; if (strongSelf.reloadView) { // 更新 Cell 高度 strongSelf.reloadView(); } }]; _productCountLabel.text = [NSString stringWithFormat:@"共%zd件商品",_subDataController.productCount]; }
-
从 TableView中爬坑, 爬坑方法: 取消 Cell 的重用, 自己创建 Cell 缓存, 如果缓存中有指定的 Cell, 直接从缓存中获取
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // 店家 Id 唯一, 根据店家 Id 来缓存 Cell NSDictionary *brandProInfo = _brands[indexPath.section - 1]; // 如果缓存中有指定的 Cell 直接从缓冲中获取 if (self.cellCache[brandProInfo[@"brandId"]]) { return (UITableViewCell *)self.cellCache[brandProInfo[@"brandId"]]; } else { // 缓存中没有, 创建并根据店家 Id 加入缓存池 WN_ProductInfoCell *productInfoCell = [WN_ProductInfoCell cell]; WN_SubDataController *dataController = self.primaryDataController.subDataControllers[indexPath.section - 1]; [productInfoCell setBrandProductsInfo:brandProInfo brandProductDataController:dataController]; __weak typeof(self)weakSelf = self; [productInfoCell setReloadView:^{ [weakSelf.tableView performSelector:@selector(reloadData) withObject:nil afterDelay:0.f inModes:@[NSDefaultRunLoopMode]]; }]; self.cellCache[brandProInfo[@"brandId"]] = productInfoCell; return productInfoCell; } }
这就是这个界面的设计思想; 能力有限, 如果您有更好的设计思想欢迎讨论