iOS 實(shí)現(xiàn)多樣式列表

設(shè)計(jì)圖.jpeg
  • 如圖,我們?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哈哈~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容