有時候 TableView 我們可能只會需要他的動態性(數據驅動)而 Cell 重用可能會導致問題
面對下面的界面我們該如何去做 ??
確認訂單_1_.jpg
界面描述:
- 這是一個關于商品的界面, 商品依據店家來分類, 所有如果使用 tableView我們可以設置 Session 為店家的個數
- 商店下的商品個數不確定, 可變, 所以我選擇再次嵌套一層 TableView, 這個 TableView 就只顯示商品
所以界面拆分如下:
確認訂單_1_.jpg
界面拆分完成, 接下來應該梳理下數據的問題, 界面流程如下:
- 程序進入到該界面, 從前一個界面中傳遞的信息中獲得所有商品的價格, 計算總價格, 推出整個頁面底部的合計金額
- 主界面請求網絡獲得用戶的地址, 用戶賬號下的禮品卡余額
- 每個商店根據自己的商店信息發起網絡請求, 請求可以使用的優惠方式(紅包, 打折卡)
- 請求獲得每個商店的優惠, 更新 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 <Foundation/Foundation.h>
@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 <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;
/**
* 優惠價格
*/
@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 <UIKit/UIKit.h> @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; } }
這就是這個界面的設計思想; 能力有限, 如果您有更好的設計思想歡迎討論??