如圖担巩,我們在開發(fā)中經(jīng)常需要完成這樣的多樣式的列表讨衣,特別是電商行業(yè)换棚,不知道大家都是怎么實現(xiàn)的?接下來我來說說我的實現(xiàn)方式反镇,不足之處或有好的想法的歡迎也分享我下固蚤,謝謝。 限于篇幅和保密問題歹茶,文章中會有些地方省略掉夕玩,這里主要講的是思路。
-
創(chuàng)建每個section布局結(jié)構(gòu)協(xié)議 RecommentDataProtocol.swift
// 數(shù)據(jù)結(jié)構(gòu)類型 enum RecommemtDataType { case banner // 輪播圖 case shoseIcon // 圖標選項 } // 表頭信息 struct SectionHeader { var sectionTitle: String init(sectionTitle: String) { self.sectionTitle = sectionTitle } // 這里可以根據(jù)需求增加標題屬性惊豺,比如 // var height: CGFloat { // return 10.0 // } } // 每個 section 對應的數(shù)據(jù)屬性 protocol RecommentDataProtocol { var dataType: RecommemtDataType { get } var rowCount: Int { get set } // 每個 section 顯示的行數(shù)燎孟,set 方法可以用 mutating func setRowCount(rowCount: Int) 代替 var size: CGSize { get } // 每一行的大小,用 UICollectionView 所以是 size var sectionHeader: SectionHeader { get } } // 設置默認值 extension RecommentDateProtocol { var rowCount: Int { get { return 1 } set { rowCount = newValue } } var size: CGSize { return CGSize(width: 0.0, height: 0.0) } }
創(chuàng)建 RecommemtDataType 對應的數(shù)據(jù)模型:BannerModel.swift尸昧、ShosenIconModel.swift揩页,這里的代碼沒啥好說的,根據(jù)服務器返回的數(shù)據(jù)結(jié)構(gòu)解析就OK
-
創(chuàng)建整個列表的數(shù)據(jù)模型 RecommentBaseModel.swift烹俗,這里包含了所有要顯示的數(shù)據(jù)集合
class RecommentBaseModel: BaseModel { var banners: [BannerModel] = [BannerModel]() var shoseIcons: [ShosenIconModel] = [ShosenIconModel]() // 網(wǎng)絡請求爆侣,這里使用的 MVVM 設計模式萍程,我選擇數(shù)據(jù)請求放在這里(model) func loadRecommentData(completeHandler: @escaping (_ message: String, _ isSuccess: Bool) -> Void) -> Void { // 解析數(shù)據(jù)得到 banners、shoseIcons 數(shù)據(jù)集 兔仰。茫负。。乎赴。朽褪。。 } }
-
創(chuàng)建 viewModel 協(xié)議 RecommentViewModelProtocol.swift无虚,關于面向協(xié)議編程的理解可以看這里
protocol RecommentViewModelProtocol { var items: [RecommentDataProtocol] { get set } var recommentModel: RecommentBaseModel { get set } }
-
創(chuàng)建 viewModel:RecommentBannerViewModel.swift、RecommentShosenIconViewModel.swift衍锚、RecommentViewModel.swift
/* * * RecommentBannerViewModel.swift友题、RecommentShosenIconViewModel.swift 要實現(xiàn) RecommentDataProtocol 協(xié)議 */ // 輪播圖 viewModel final class RecommentBannerViewModel: RecommentDataProtocol { var dataType: RecommemtDateType { return .banner } var size: CGSize { return CGSize(width: SYSTEMMACROS_SCREEN_WIDTH, height: FITSCREEN(f: 190.0)) } var sectionHeader: SectionHeader = SectionHeader(sectionTitle: "") var banners: [BannerModel] = [] } // icon 選項 viewModel final class RecommentShosenIconViewModel: RecommentDataProtocol { var dataType: RecommemtDateType { return .shoseIcon } var sectionHeader: SectionHeader = SectionHeader( sectionTitle: "") var size: CGSize { return CGSize(width: SYSTEMMACROS_SCREEN_WIDTH, height: FITSCREEN(f: 90.0)) } var shoseIcons: [ShosenIconModel] = [ShosenIconModel]() } // 推薦列表 viewModel final class RecommentViewModel: RecommentViewModelProtocol { var items: [RecommentDateProtocol] = [] var recommentModel: RecommentBaseModel = RecommentBaseModel() // viewModel 關聯(lián) Model func loadRecommentData(completeHandler: @escaping (_ message: String, _ isSuccess: Bool) -> Void) -> Void { // 加載數(shù)據(jù) self.recommentModel.loadRecommentDate { (message: String, isSuccess: Bool) in self.items.removeAll() // 獲取輪播圖信息 let bannerViewModel: RecommentBannerViewModel = RecommentBannerViewModel() bannerViewModel.banners = self.recommentModel.banners self.items.append(bannerViewModel) // 獲取選項信息 let shoseIconViewModel = RecommentShosenIconViewModel() shoseIconViewModel.shoseIcons = self.recommentModel.shoseIcons self.items.append(shoseIconViewModel) completeHandler(message, isSuccess) } } }
-
創(chuàng)建 banner 和 shoseIcon 要顯示的 View
/** * 列表用的是 UICollectionView 所以這里的 View 都繼承自 UICollectionViewCell */ // 輪播圖界面 class RecommentBannerCollectionViewCell: UICollectionViewCell { // TODO:關于界面的實現(xiàn)細節(jié)這里就不寫了 var bannerViewModel: RecommentBannerViewModel? { // 關聯(lián)viewModel didSet { guard (bannerViewModel?.banners.count)! > 0 else { return } // TODO:給界面賦值刷新顯示 } } // icon 選項界面 class ShoseIconsCollectionViewCell: UICollectionViewCell { // TODO:關于界面的實現(xiàn)細節(jié)這里就不寫了 var shoseIconViewModel: RecommentShosenIconViewModel = RecommentShosenIconViewModel() { didSet { // TODO:給界面賦值刷新顯示 } } }
-
創(chuàng)建 RecommentViewController.swift
fileprivate let kBannerCellIdentifier = "kBannerCellIdentifier" fileprivate let kShoseCellIdentifier = "kShoseCellIdentifier" // MARK: - life cyclic class RecommentViewController: BaseViewController { var viewModel: RecommentViewModel = RecommentViewModel() // 關聯(lián)viewModel var recommentCollectionView: UICollectionView? // 實現(xiàn)細節(jié)省略。戴质。度宦。 override func viewDidLoad() { super.viewDidLoad() setupView() // 初始化添加 recommentCollectionView } func setupView() { initRecommentCollectionView() } // MARK: init subview private func initRecommentCollectionView() -> Void { let layout = UICollectionViewFlowLayout() recommentCollectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout) recommentCollectionView?.backgroundColor = .white recommentCollectionView?.autoresizingMask = [.flexibleHeight, .flexibleWidth] recommentCollectionView?.showsVerticalScrollIndicator = false recommentCollectionView?.showsHorizontalScrollIndicator = false recommentCollectionView?.alwaysBounceVertical = true recommentCollectionView?.delegate = self recommentCollectionView?.dataSource = self recommentCollectionView?.register(RecommentBannerCollectionViewCell.self, forCellWithReuseIdentifier: kBannerCellIdentifier) recommentCollectionView?.register(ShoseIconsCollectionViewCell.self, forCellWithReuseIdentifier: kShoseCellIdentifier) recommentCollectionView?.es_addPullToRefresh { // 下拉刷新 ProgressHub.show() self.viewModel.loadRecommentData(completeHandler: { (message: String, isSuccess: Bool) in self.recommentCollectionView?.es_stopPullToRefresh() guard isSuccess else { ProgressHub.showStatus(statusString: message) return } ProgressHub.dismiss() self.recommentCollectionView?.reloadData() }) } recommentCollectionView?.es_startPullToRefresh() recommentCollectionView?.es_addInfiniteScrolling { // TODO:上拉加載更多(這里只加載推薦商品) self.recommentCollectionView?.es_stopLoadingMore() } view.addSubview(recommentCollectionView!) } } // 布局 extension RecommentViewController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { // 關鍵點,省略大量 if 或 switch let item: RecommentDateProtocol = viewModel.items[indexPath.section] return item.size } } // 實現(xiàn)代理 extension RecommentViewController: UICollectionViewDataSource { func numberOfSections(in collectionView: UICollectionView) -> Int { // 關鍵點告匠,省略大量 if 或 switch return viewModel.items.count } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { // 關鍵點戈抄,省略大量 if 或 switch let item: RecommentDateProtocol = viewModel.items[section] return item.rowCount } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let item: RecommentDateProtocol = viewModel.items[indexPath.section] // 這里也可以用抽象類代替 switch 的實現(xiàn),但考慮到 cell 可能存在的各種操作事件交互后专,增加數(shù)據(jù)與事件關聯(lián)的復雜度划鸽,暫時選擇 switch switch item.dateType { case .banner: let cell: RecommentBannerCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: kBannerCellIdentifier, for: indexPath) as! RecommentBannerCollectionViewCell cell.bannerViewModel = item as? RecommentBannerViewModel return cell case .shoseIcon: let cell: ShoseIconsCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: kShoseCellIdentifier, for: indexPath) as! ShoseIconsCollectionViewCell cell.shoseIconViewModel = item as! RecommentShosenIconViewModel return cell default: break } return UICollectionViewCell() } }
好啦,主要的過程已經(jīng)實現(xiàn)完成戚哎,其實這個過程主要實現(xiàn)思想就是狀態(tài)設計模式(statue pattern)裸诽,大家可以去具體了解下該設計模式。任何時候抽象的目的都是解耦型凳、易擴展丈冬,這里減少了數(shù)據(jù)與界面的耦合性,同時當需要增加新的類型的時候甘畅,只要在 RecommemtDataType 增加類型埂蕊,實現(xiàn)對應的 viewModel 實現(xiàn) RecommentDataProtocol 協(xié)議,然后再在 UICollectionViewDataSource 的代理中實現(xiàn)對應的 switch 分支即可疏唾,更易擴展蓄氧。當然在抽象時也會增加文件量,需要維護更多的文件荸实,所以我們在寫代碼過程中需要根據(jù)需求匀们,自我衡量,選擇適當?shù)姆绞阶几瑫r定期 review 和 重構(gòu)是有必要的泄朴。
PS:感覺寫得不是很順暢重抖,希望能慢慢鍛煉中得到改善O(∩_∩)O哈哈~