Swift 实现多样式列表

Swift 实现多样式列表_第1张图片
设计图.jpeg
  • 如图,我们在开发中经常需要完成这样的多样式的列表,特别是电商行业,不知道大家都是怎么实现的?接下来我来说说我的实现方式,不足之处或有好的想法的欢迎也分享我下,谢谢。 限于篇幅和保密问题,文章中会有些地方省略掉,这里主要讲的是思路。

  • 创建每个section布局结构协议 RecommentDataProtocol.swift

      // 数据结构类型
      enum RecommemtDataType {
          case banner             // 轮播图
          case shoseIcon          // 图标选项
      }
      // 表头信息
      struct SectionHeader {
          var sectionTitle: String
          init(sectionTitle: String) {
               self.sectionTitle =  sectionTitle
          }
          // 这里可以根据需求增加标题属性,比如
          // var height: CGFloat {
          //    return 10.0
          // }
      }
      // 每个 section 对应的数据属性
      protocol RecommentDataProtocol {
          var dataType: RecommemtDataType { get }
          var rowCount: Int { get set }   // 每个 section 显示的行数,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)
          }
      }
    
  • 创建 RecommemtDataType 对应的数据模型:BannerModel.swift、ShosenIconModel.swift,这里的代码没啥好说的,根据服务器返回的数据结构解析就OK

  • 创建整个列表的数据模型 RecommentBaseModel.swift,这里包含了所有要显示的数据集合

    class RecommentBaseModel: BaseModel { 
        var banners: [BannerModel]         = [BannerModel]()
        var shoseIcons: [ShosenIconModel]  = [ShosenIconModel]()
        // 网络请求,这里使用的 MVVM 设计模式,我选择数据请求放在这里(model)
        func loadRecommentData(completeHandler: @escaping (_ message: String, _ isSuccess: Bool) -> Void) -> Void {
        // 解析数据得到 banners、shoseIcons 数据集 
        。。。。。。
        }
    }
    
  • 创建 viewModel 协议 RecommentViewModelProtocol.swift,关于面向协议编程的理解可以看这里

    protocol RecommentViewModelProtocol {
        var items: [RecommentDataProtocol] { get set }
        var recommentModel: RecommentBaseModel { get set }
    }
    
  • 创建 viewModel:RecommentBannerViewModel.swift、RecommentShosenIconViewModel.swift、RecommentViewModel.swift

    /* *
     * RecommentBannerViewModel.swift、RecommentShosenIconViewModel.swift 要实现 RecommentDataProtocol 协议 
     */
    // 轮播图 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 关联 Model
        func loadRecommentData(completeHandler: @escaping (_ message: String, _ isSuccess: Bool) -> Void) -> Void {
              // 加载数据
              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)
          }
        }
    }
    
  • 创建 banner 和 shoseIcon 要显示的 View

    /**
     * 列表用的是 UICollectionView 所以这里的 View 都继承自 UICollectionViewCell
     */
    // 轮播图界面
    class RecommentBannerCollectionViewCell: UICollectionViewCell { 
        // TODO:关于界面的实现细节这里就不写了
        var bannerViewModel: RecommentBannerViewModel? {        // 关联viewModel
           didSet {
               guard (bannerViewModel?.banners.count)! > 0 else {
                  return
               }
               // TODO:给界面赋值刷新显示
           }
    }
    // icon 选项界面
    class ShoseIconsCollectionViewCell: UICollectionViewCell { 
        // TODO:关于界面的实现细节这里就不写了
        var shoseIconViewModel: RecommentShosenIconViewModel = RecommentShosenIconViewModel() {
           didSet { 
              // TODO:给界面赋值刷新显示
           }
        }
    }
    
  • 创建 RecommentViewController.swift

    fileprivate let kBannerCellIdentifier        = "kBannerCellIdentifier"
    fileprivate let kShoseCellIdentifier         = "kShoseCellIdentifier" 
    // MARK:  - life cyclic
    class RecommentViewController: BaseViewController {
        var viewModel: RecommentViewModel = RecommentViewModel()   // 关联viewModel
        var recommentCollectionView: UICollectionView?             // 实现细节省略。。。
        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
       }
    } 
    // 实现代理
    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 的实现,但考虑到 cell 可能存在的各种操作事件交互,增加数据与事件关联的复杂度,暂时选择 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()
        }
    }
    
  • 好啦,主要的过程已经实现完成,其实这个过程主要实现思想就是状态设计模式(statue pattern),大家可以去具体了解下该设计模式。任何时候抽象的目的都是解耦、易扩展,这里减少了数据与界面的耦合性,同时当需要增加新的类型的时候,只要在 RecommemtDataType 增加类型,实现对应的 viewModel 实现 RecommentDataProtocol 协议,然后再在 UICollectionViewDataSource 的代理中实现对应的 switch 分支即可,更易扩展。当然在抽象时也会增加文件量,需要维护更多的文件,所以我们在写代码过程中需要根据需求,自我衡量,选择适当的方式,同时定期 review 和 重构是有必要的。

你可能感兴趣的:(Swift 实现多样式列表)