有時候 TableView 我們可能只會需要他的動態(tài)性(數(shù)據(jù)驅(qū)動)而 Cell 重用可能會導(dǎo)致問題
面對下面的界面我們該如何去做 ??
界面描述:
- 這是一個關(guān)于商品的界面, 商品依據(jù)店家來分類, 所有如果使用 tableView我們可以設(shè)置 Session 為店家的個數(shù)
- 商店下的商品個數(shù)不確定, 可變, 所以我選擇再次嵌套一層 TableView, 這個 TableView 就只顯示商品
所以界面拆分如下:
界面拆分完成, 接下來應(yīng)該梳理下數(shù)據(jù)的問題, 界面流程如下:
- 程序進(jìn)入到該界面, 從前一個界面中傳遞的信息中獲得所有商品的價格, 計算總價格, 推出整個頁面底部的合計金額
- 主界面請求網(wǎng)絡(luò)獲得用戶的地址, 用戶賬號下的禮品卡余額
- 每個商店根據(jù)自己的商店信息發(fā)起網(wǎng)絡(luò)請求, 請求可以使用的優(yōu)惠方式(紅包, 打折卡)
- 請求獲得每個商店的優(yōu)惠, 更新 UI, 將不包含的優(yōu)惠從界面中移除
- 當(dāng)用戶在商家下選擇任意或取消一種優(yōu)惠時, 商家底部的實(shí)付要做相應(yīng)的更新, 同時底部的總價要做相應(yīng)的更新
- 當(dāng)選擇好商家優(yōu)惠, 用戶再次選擇余額或禮品卡同樣樣做相應(yīng)的 UI 及數(shù)據(jù)更新, 防止總價小于優(yōu)惠的價格導(dǎo)致總價出現(xiàn)負(fù)數(shù)的情況
- 項目后期拆分訂單被取消, 替代的是商家的運(yùn)費(fèi), 不同的商家要請求獲得運(yùn)費(fèi), 這個請求的運(yùn)費(fèi)也會影響商家下的實(shí)付金額, 界面下的合計金額
根據(jù)上面的流程, 我做如下的設(shè)計:
- 整個 ViewController 的數(shù)據(jù)由一個 PrimaryDataController 控制
- 每個商店的數(shù)據(jù)由一個 SubDataController 控制
- PrimaryDataController 有個數(shù)組包含 SubDataController, 有多少商店就有多少 SubDataController 對象
- 每個藍(lán)色區(qū)域的 Cell 綁定一個 SubDataController(注意這是個埋坑點(diǎn))
- 因?yàn)楦鱾€不同的店家的優(yōu)惠要分別異步請求, 請求結(jié)束后要刷新界面, 我們還要監(jiān)聽店家優(yōu)惠金額的改變, 監(jiān)聽變化有幾種可選措施: (1). KVO, 通知 (2). 代理, (3). Block; 首先 KVO, 通知可以放棄, 因?yàn)樗麄兪且粚Χ嗟年P(guān)系, 更改優(yōu)惠方法發(fā)出一條通知, 而整個界面所有的藍(lán)色 Cell 都是監(jiān)聽者, 這樣無法確定哪一個應(yīng)該發(fā)生相應(yīng)的更改. 代理, Block 的回調(diào)觸發(fā)都是一對一的, 這兩種作為可選操作, 這里我選擇的是 Block
- 因?yàn)榈?步我選擇的 Block 觸發(fā)回調(diào), 看第四步有坑, 這里的坑是指藍(lán)色 Cell 指定相同的Identifier, 所以藍(lán)色 Cell 是可以重用的, 如果一重用, SubDataController 的回調(diào)綁定關(guān)系就會產(chǎn)生問題, 這個地方是我掉的一個坑, 還有如果店家的優(yōu)惠信息請求結(jié)束要相應(yīng)的對優(yōu)惠進(jìn)行顯示和隱藏, 就是要更新藍(lán)色 Cell 的高度, 這樣難免調(diào)用 TableView 的 reloadData, 如果一調(diào)用 reloadData, 界面就會重新賦值, 所以會面臨一個問題, 有多個店家, 假設(shè)店家優(yōu)惠請求結(jié)束是依次結(jié)束, 這樣當(dāng)用戶下滑時, 第一個藍(lán)色 Cell 進(jìn)入重用池, 然后再從重用池中取出顯示另一個店家商品信息, 這樣第一個店家優(yōu)惠信息請求結(jié)束實(shí)際是在這個顯示這個店家商品信息刷新, 而且會一個接一個結(jié)束, 不停地 reloadData, 導(dǎo)致問題
具體從代碼中了解如何解決:
首先創(chuàng)建 PrimaryDataController
#import <Foundation/Foundation.h>
@interface WN_PrimaryDataController : NSObject
@property (nonatomic, strong) NSDictionary *shoppingCartsInfo;
/**
* 每個品牌 Section 的數(shù)據(jù)控制器
*/
@property (nonatomic, strong) NSArray *subDataControllers;
/**
* 確認(rèn)訂單中總價錢
*/
@property (nonatomic, strong) NSDecimalNumber *totalPrice;
/**
* 除去優(yōu)惠方法的商品總價
*/
@property (nonatomic, strong, readonly) NSDecimalNumber *originalTotalPrice;
/**
* 優(yōu)惠價格
*/
@property (nonatomic, strong) NSDecimalNumber *privilegePrice;
/**
* 更改總價后觸發(fā) View 中的回調(diào)
*/
@property (nonatomic, copy) void (^changeTotalPriceCallBack)(NSDecimalNumber *totalPrice);
/**
* 確認(rèn)訂單中商品總數(shù)
*/
@property (nonatomic, assign, readonly) NSUInteger productCount;
/**
* 用戶輸入的使用的禮品卡金額
*/
@property (nonatomic, copy) NSString *giftCardValue;
/**
* 總的禮品卡余額
*/
@property (nonatomic, copy, readonly) NSString *totalGiftCardValue;
/**
* 用戶輸入的使用的用戶余額
*/
@property (nonatomic, copy) NSString *balanceValue;
/**
* 運(yùn)費(fèi)
*/
@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)();
/**
* 某一個品牌下添加紅包或者打折卡導(dǎo)致總優(yōu)惠發(fā)送變化
*
* @param price 某個品牌選擇紅包或者打折卡產(chǎn)生的優(yōu)惠金額
*/
- (void)addPrivilegePrice:(NSDecimalNumber *)price;
/**
* 某個品牌下移除紅包或者打折卡導(dǎo)致總優(yōu)發(fā)生變化
*
* @param price 某個品牌選擇紅包或者打折卡產(chǎn)生的優(yōu)惠金額
*/
- (void)removePrivilegePrice:(NSDecimalNumber *)price;
@end
SubDataController:
#import <Foundation/Foundation.h>
#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;
/**
* 優(yōu)惠價格
*/
@property (nonatomic, strong, readonly) NSDecimalNumber *privilegePrice;
/**
* 更改總價后觸發(fā) View 中的回調(diào)
*/
@property (nonatomic, copy) void (^changeTotalPriceCallBack)(NSDecimalNumber *totalPrice);
/**
* 優(yōu)惠更改后觸發(fā) View 中的回調(diào)
*/
@property (nonatomic, copy) void (^changePrivilegePriceCallBack)(NSDecimalNumber *privilegePrice);
@property (nonatomic, copy) void (^changeFreightPriceCallBack)(NSString *freightPrice);
/**
* 更改優(yōu)惠后 PrimaryDataController 中的回調(diào)
*/
@property (nonatomic, copy) void (^changePrivilegePricePrimaryDataControllerCallBack)(NSDecimalNumber *privilegePrice);
/**
* 該品牌下每件商品的禮品卡使用比例
*/
@property (nonatomic, strong) NSMutableArray *giftCardUsageRanges;
/**
* 設(shè)置該品牌下所有的商品信息
*/
- (void)setCartItemInfo:(NSDictionary *)dic;
/**
* 該品牌下商品的總數(shù)
*/
@property (nonatomic, assign, readonly) NSInteger productCount;
/**
* 可用紅包數(shù)組
*/
@property (nonatomic, strong, readonly) NSArray *redPackets;
/**
* 可用紅包個數(shù)
*/
@property (nonatomic, assign, readonly) NSUInteger validityRedPacketsCount;
/**
* 紅包模型
*/
@property (nonatomic, strong, readonly) WN_RedpacketModel *redpacketModel;
/**
* 可用打折卡數(shù)組
*/
@property (nonatomic, strong, readonly) NSArray *discountCards;
/**
* 可用打折卡個數(shù)
*/
@property (nonatomic, assign, readonly) NSUInteger validityDiscountCardCount;
/**
* 打折卡模型
*/
@property (nonatomic, strong, readonly) WN_DiscountCardModel *discountCardModel;
/**
* 發(fā)票信息
*/
@property (nonatomic, copy) NSDictionary *invoiceInfoDictionary;
/**
* 運(yùn)費(fèi)
*/
@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);
/**
* 根據(jù)品牌價值完紅包打折卡后的回調(diào)
* @param callBack 根據(jù)品牌價值完紅包打折卡后的回調(diào)
* redPackets 紅包數(shù)組
* redPackCount 紅包個數(shù)
* discounts 打折卡數(shù)組
* discountCardCount 打折卡個數(shù)
* redpacketModel 與本品牌關(guān)聯(lián)的紅包模型
* discountCardModel 與本品牌關(guān)聯(lián)的打折卡模型
* freight 運(yùn)費(fèi)
*/
- (void)loadDataFinishedCallBack:(void (^)(NSArray *redPackets,
NSUInteger redPackCount,
NSArray *discounts,
NSUInteger discountCardCount,
WN_RedpacketModel *redpacketModel,
WN_DiscountCardModel *discountCardModel,
NSString *freight)) callBack;
@end
TableViewController 中:
- 根據(jù)數(shù)據(jù)創(chuàng)建 PrimaryDataController, SubDataController:
- (WN_PrimaryDataController *)primaryDataController {
if (!_primaryDataController) {
// 創(chuàng)建主數(shù)據(jù)控制器
_primaryDataController = [[WN_PrimaryDataController alloc] init];
NSMutableArray *subDataControllers = [NSMutableArray arrayWithCapacity:_brands.count];
__weak typeof(self)weakSelf = self;
__weak typeof(_primaryDataController)weakPrimayDataController = _primaryDataController;
// 根據(jù)每個店家的數(shù)據(jù)創(chuàng)建店家的數(shù)據(jù)控制器
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;
}
-
藍(lán)色 Cell
#import <UIKit/UIKit.h> @class WN_SubDataController, WN_PrimaryDataController; @interface WN_ProductInfoCell : UITableViewCell @property (nonatomic, strong, readonly) NSDictionary *info; // 對應(yīng)一個數(shù)據(jù)控制器 @property (nonatomic, strong, readonly) WN_SubDataController *subDataController; @property (nonatomic, copy) void (^reloadView)(); /** * 設(shè)置品牌商品信息, 配置一個數(shù)據(jù)控制類 * * @param info 商品信息 * @param dataController 數(shù)據(jù)控制類 */ - (void)setBrandProductsInfo:(NSDictionary *)info brandProductDataController:(WN_SubDataController *)dataController; + (instancetype)cell; @end
-
藍(lán)色 Cell 綁定子數(shù)據(jù)控制器, 并綁定數(shù)據(jù)回調(diào)
- (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) { // 運(yùn)費(fèi)獲得回調(diào) weakSelf.freightMaskView.hidden = loading ? NO: YES; }]; [_subDataController setChangeTotalPriceCallBack:^(NSDecimalNumber *totalPrice) { // 總價發(fā)生變化回調(diào) weakSelf.totalPriceLabel.text = [NSString stringWithFormat:@"%.2f",[totalPrice floatValue]]; }]; [_subDataController setChangePrivilegePriceCallBack:^(NSDecimalNumber *privliegePrice) { // 優(yōu)惠金額發(fā)送變化 weakSelf.privilegeLabel.text = [NSString stringWithFormat:@"優(yōu)惠%.2f 元",[privliegePrice floatValue]]; }]; [_subDataController setChangeFreightPriceCallBack:^(NSString *freight) { [weakSelf.freightLabel setText:[NSString stringWithFormat:@"運(yùn)費(fèi) %@元",freight]]; }]; [_subDataController loadDataFinishedCallBack:^(NSArray *redPackets, NSUInteger redPackCount, NSArray *discounts, NSUInteger discountCardCount, WN_RedpacketModel *redpacketModel, WN_DiscountCardModel *discountCardModel, NSString *freight) { __strong typeof(weakSelf)strongSelf = weakSelf; // 配置優(yōu)惠 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 的重用, 自己創(chuàng)建 Cell 緩存, 如果緩存中有指定的 Cell, 直接從緩存中獲取
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // 店家 Id 唯一, 根據(jù)店家 Id 來緩存 Cell NSDictionary *brandProInfo = _brands[indexPath.section - 1]; // 如果緩存中有指定的 Cell 直接從緩沖中獲取 if (self.cellCache[brandProInfo[@"brandId"]]) { return (UITableViewCell *)self.cellCache[brandProInfo[@"brandId"]]; } else { // 緩存中沒有, 創(chuàng)建并根據(jù)店家 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; } }
這就是這個界面的設(shè)計思想; 能力有限, 如果您有更好的設(shè)計思想歡迎討論??