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

設計圖.jpeg
  • 如圖担巩,我們在開發(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哈哈~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市祖灰,隨后出現(xiàn)的幾起案子钟沛,更是在濱河造成了極大的恐慌,老刑警劉巖局扶,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恨统,死亡現(xiàn)場離奇詭異,居然都是意外死亡三妈,警方通過查閱死者的電腦和手機畜埋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來畴蒲,“玉大人悠鞍,你說我怎么就攤上這事∧T铮” “怎么了咖祭?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蔫骂。 經(jīng)常有香客問我么翰,道長,這世上最難降的妖魔是什么辽旋? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任浩嫌,我火速辦了婚禮,結(jié)果婚禮上戴已,老公的妹妹穿的比我還像新娘固该。我一直安慰自己,他們只是感情好糖儡,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布伐坏。 她就那樣靜靜地躺著,像睡著了一般握联。 火紅的嫁衣襯著肌膚如雪桦沉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天金闽,我揣著相機與錄音纯露,去河邊找鬼。 笑死代芜,一個胖子當著我的面吹牛埠褪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼钞速,長吁一口氣:“原來是場噩夢啊……” “哼贷掖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起渴语,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤苹威,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后驾凶,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體牙甫,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年调违,在試婚紗的時候發(fā)現(xiàn)自己被綠了窟哺。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡技肩,死狀恐怖脏答,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情亩鬼,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布阿蝶,位于F島的核電站雳锋,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏羡洁。R本人自食惡果不足惜玷过,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望筑煮。 院中可真熱鬧辛蚊,春花似錦、人聲如沸真仲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽秸应。三九已至虑凛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間软啼,已是汗流浹背桑谍。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留祸挪,地道東北人锣披。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親雹仿。 傳聞我的和親對象是個殘疾皇子增热,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容