美文网首页iOS痕迹
Swift 实现多样式列表

Swift 实现多样式列表

作者: 浅寒 | 来源:发表于2017-09-11 14:40 被阅读0次
    设计图.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 实现多样式列表

        本文链接:https://www.haomeiwen.com/subject/pxhfsxtx.html