
如圖,我們?cè)陂_發(fā)中經(jīng)常需要完成這樣的多樣式的列表,特別是電商行業(yè),不知道大家都是怎么實(shí)現(xiàn)的?接下來我來說說我的實(shí)現(xiàn)方式,不足之處或有好的想法的歡迎也分享我下,謝謝。 限于篇幅和保密問題,文章中會(huì)有些地方省略掉,這里主要講的是思路。
-
創(chuàng)建每個(gè)section布局結(jié)構(gòu)協(xié)議 RecommentDataProtocol.swift
// 數(shù)據(jù)結(jié)構(gòu)類型 enum RecommemtDataType { case banner // 輪播圖 case shoseIcon // 圖標(biāo)選項(xiàng) } // 表頭信息 struct SectionHeader { var sectionTitle: String init(sectionTitle: String) { self.sectionTitle = sectionTitle } // 這里可以根據(jù)需求增加標(biāo)題屬性,比如 // var height: CGFloat { // return 10.0 // } } // 每個(gè) section 對(duì)應(yīng)的數(shù)據(jù)屬性 protocol RecommentDataProtocol { var dataType: RecommemtDataType { get } var rowCount: Int { get set } // 每個(gè) section 顯示的行數(shù),set 方法可以用 mutating func setRowCount(rowCount: Int) 代替 var size: CGSize { get } // 每一行的大小,用 UICollectionView 所以是 size var sectionHeader: SectionHeader { get } } // 設(shè)置默認(rèn)值 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 對(duì)應(yīng)的數(shù)據(jù)模型:BannerModel.swift、ShosenIconModel.swift,這里的代碼沒啥好說的,根據(jù)服務(wù)器返回的數(shù)據(jù)結(jié)構(gòu)解析就OK
-
創(chuàng)建整個(gè)列表的數(shù)據(jù)模型 RecommentBaseModel.swift,這里包含了所有要顯示的數(shù)據(jù)集合
class RecommentBaseModel: BaseModel { var banners: [BannerModel] = [BannerModel]() var shoseIcons: [ShosenIconModel] = [ShosenIconModel]() // 網(wǎng)絡(luò)請(qǐng)求,這里使用的 MVVM 設(shè)計(jì)模式,我選擇數(shù)據(jù)請(qǐng)求放在這里(model) func loadRecommentData(completeHandler: @escaping (_ message: String, _ isSuccess: Bool) -> Void) -> Void { // 解析數(shù)據(jù)得到 banners、shoseIcons 數(shù)據(jù)集 。。。。。。 } } -
創(chuàng)建 viewModel 協(xié)議 RecommentViewModelProtocol.swift,關(guān)于面向協(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 要實(shí)現(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 選項(xiàng) 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 關(guān)聯(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) // 獲取選項(xiàng)信息 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:關(guān)于界面的實(shí)現(xiàn)細(xì)節(jié)這里就不寫了 var bannerViewModel: RecommentBannerViewModel? { // 關(guān)聯(lián)viewModel didSet { guard (bannerViewModel?.banners.count)! > 0 else { return } // TODO:給界面賦值刷新顯示 } } // icon 選項(xiàng)界面 class ShoseIconsCollectionViewCell: UICollectionViewCell { // TODO:關(guān)于界面的實(shí)現(xiàn)細(xì)節(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() // 關(guān)聯(lián)viewModel var recommentCollectionView: UICollectionView? // 實(shí)現(xiàn)細(xì)節(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 { // 關(guān)鍵點(diǎn),省略大量 if 或 switch let item: RecommentDateProtocol = viewModel.items[indexPath.section] return item.size } } // 實(shí)現(xiàn)代理 extension RecommentViewController: UICollectionViewDataSource { func numberOfSections(in collectionView: UICollectionView) -> Int { // 關(guān)鍵點(diǎn),省略大量 if 或 switch return viewModel.items.count } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { // 關(guān)鍵點(diǎn),省略大量 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 的實(shí)現(xiàn),但考慮到 cell 可能存在的各種操作事件交互,增加數(shù)據(jù)與事件關(guān)聯(lián)的復(fù)雜度,暫時(shí)選擇 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)實(shí)現(xiàn)完成,其實(shí)這個(gè)過程主要實(shí)現(xiàn)思想就是狀態(tài)設(shè)計(jì)模式(statue pattern),大家可以去具體了解下該設(shè)計(jì)模式。任何時(shí)候抽象的目的都是解耦、易擴(kuò)展,這里減少了數(shù)據(jù)與界面的耦合性,同時(shí)當(dāng)需要增加新的類型的時(shí)候,只要在 RecommemtDataType 增加類型,實(shí)現(xiàn)對(duì)應(yīng)的 viewModel 實(shí)現(xiàn) RecommentDataProtocol 協(xié)議,然后再在 UICollectionViewDataSource 的代理中實(shí)現(xiàn)對(duì)應(yīng)的 switch 分支即可,更易擴(kuò)展。當(dāng)然在抽象時(shí)也會(huì)增加文件量,需要維護(hù)更多的文件,所以我們?cè)趯懘a過程中需要根據(jù)需求,自我衡量,選擇適當(dāng)?shù)姆绞?,同時(shí)定期 review 和 重構(gòu)是有必要的。
PS:感覺寫得不是很順暢,希望能慢慢鍛煉中得到改善O(∩_∩)O哈哈~