使用RXSwift撸一个CollectionView -- part2

615 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情


前言

又来撸RXSwift了

如果您还没有阅读上一部分,它可以在这里找到。在上一篇中,我们实现了如何设置只有一种cell的 collectionview。

这一part,咱们来用RXSwift写一个多section的Collectionview

还是要引入

pod RxDataSources

这个库可以帮我们实现更多功能,使用它可以大大减少我们的工作量。更多功能可以自行百度。

正文

项目的目录结构

截屏2022-05-31 上午11.26.24.png

第一步 定义Model

咱们在Model文件夹中创建 Product.swift模型

struct Product {
    let name: String// 名称
    let price: String// 价格
}

第二步 视图模型(viewModel)

在ViewModel文件夹下新建ProductViewModel.swift 然后导入

import RxSwift
import RxDataSources

然后定义一个section 使用enum

enum CollectionSection{
    case main(String)
}

这个 相关值类型的枚举,咱们定义为string类型

然后定义Item

enum CollectionItem {
    case Prod(info: Product)
    
}

同样这个Item的相关类型枚举,设置为 Product模型

然后咱们来定义 SectionModel

typealias SectionOfProduct = SectionModel<CollectionSection,CollectionItem>

这里的SectionModel是来自RxDataSources,它有两个参数,一个是Section,另一个是Item,咱们就用上面定义好的CollectionSectionCollectionItem传入

所以,SectionOfProduct 就相当于一个section里的所有内容了,初始化它也十分简单

SectionOfProduct(model: .main(xxx), items: [])

最后,咱们做模拟数据

class ProductViewModel {
    
    let items = PublishSubject<[SectionOfProduct]>()

    
    func getInformation() {
        var subItems_iphone = [CollectionItem]()
        
        subItems_iphone = ([
            .Prod(info: Product(name: "iPhone11", price: "100")),
            .Prod(info: Product(name: "iPhone12", price: "100")),
            .Prod(info: Product(name: "iPhone13", price: "100")),
        ])
        
        var subItems_ipad = [CollectionItem]()
        
        subItems_ipad = ([
            .Prod(info: Product(name: "ipad5", price: "100")),
            .Prod(info: Product(name: "ipad6", price: "100")),
        ])
        
        items.onNext([SectionOfProduct(model: .main("iphone"), items: subItems_iphone),
                      SectionOfProduct(model: .main("ipad"), items: subItems_ipad)
                     ])
    }

}

在这个Viewmodel里,我模拟设置了两个section 一个是iphone,另一个是ipad,同时,每个section中又有一组数据。这样,就完成了数据的模拟。

第三步 视图(View)

咱们在View文件夹下创建一个ProductVC

同样的设置 bagviewModel 和一些基本参数

    var collectionView: UICollectionView!
    var disposeBag = DisposeBag()//初始化清除包
    private let viewModel = ProductViewModel()
    
    let SCREEN_WIDTH = UIScreen.main.bounds.size.width
    let SCREEN_HEIGHT = UIScreen.main.bounds.size.height

然后懒加载一个 dataSource

private lazy var dataSource = RxCollectionViewSectionedReloadDataSource<SectionOfProduct>(configureCell: {(ds,collectionView,indexPath,ele) -> UICollectionViewCell in
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell",for: indexPath) as! MyCollectionViewCell
        switch ele {
        case .Prod(let info):
            cell.viewModel = info
            break
        }
        
        return cell
    },configureSupplementaryView: {(ds,cv,kind,indexPath) in
        
        
        let section = cv.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "Section", for: indexPath) as! MySectionHeader
        
        let viewModel = ds[indexPath.section]
        let model = viewModel.model
        
        
        switch model {
        case .main(let str):
            section.headerLB.text = "\(str)"
            break
            
        }  
        return section
        
    })

这里的 dataSource 加载和 tableview有些许不同。

dataSource 数据源初始化的时候,可以将cellheaderviewfoodview同时 Reuse,所以,在这里就节省了不少代码。

但是关键不要写错,RXSwift就是这点特别不好,代码提示做的真烂!

最后就是UI设置和bind设置了

func  creatUI(){
        // 定义布局方式以及单元格大小
        let flowLayout = UICollectionViewFlowLayout()
        flowLayout.sectionInset = UIEdgeInsets.init(top: 0, left: 5, bottom: 0, right: 5)
        flowLayout.headerReferenceSize = CGSize(width: self.view.frame.width, height: 40)

        flowLayout.itemSize = CGSize(width: (SCREEN_WIDTH-50)/3.0, height: 180)
         
        // 创建集合视图
        self.collectionView = UICollectionView(frame: self.view.frame,
                                               collectionViewLayout: flowLayout)
        self.collectionView.backgroundColor = UIColor.white
         
        // 创建一个重用的单元格
        self.collectionView.register(UINib.init(nibName: "MyCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "Cell")
        
        // 创建一个重用的分区头
        self.collectionView.register(UINib.init(nibName: "MySectionHeader", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "Section")
        
        self.view.addSubview(self.collectionView!)
    }

设置UI时,出于方便,我都用的xib,如果用代码的话,注册cell这里要变一下 注意!

func bindView(){
        
        viewModel.items.bind(to: collectionView.rx.items(dataSource: dataSource)).disposed(by: disposeBag)
        
        viewModel.getInformation()
        
    }

bind比较简单,直接将viewModel.items 绑定到 collectionViewdataSource

最后进行 getInformation 模拟请求

就基本上完成了

可以看看效果图

截屏2022-05-31 上午11.51.42.png

结语

总体来说 在collectionview上使用 RxDataSources 进行多section的设置,代码量少了,但是代码RXSwift上的代码提示也少了,这对初学者十分不友好。希望能会改进把。