-
rxSwift + mvvm 구조로 콜렉션뷰 설계하기 (2)iOS/mvvm + RxSwift 스터디 2018. 12. 4. 21:59반응형
https://sesang06.tistory.com/98
전에 포스트했던 콜렉션뷰는 섹션이 하나도 없었습니다.
RxDatasource을 임포트해
콜렉션뷰에 섹션을 추가해보겠습니다.
protocol CodeBlock : Equatable {
var codeBlockName : String { get }
var codeBlockId : Int { get }
}
extension CodeBlock {
static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.codeBlockName == rhs.codeBlockName
}
}
protocol CodeSentence {
associatedtype T
var selectedCodeBlocks : Variable<[T]> { get set }
var candidateCodeBlocks : Variable<[T]> { get }
// var answerCodeBlocks : [T] { get }
}
//
//extension CodeSentence {
// func isRightSentence() -> Bool {
// return selectedCodeBlocks.value == answerCodeBlocks
// }
//
//}
class EnglishCodeBlock : CodeBlock {
var codeBlockName: String
var codeBlockId: Int
init(name: String){
self.codeBlockName = name
self.codeBlockId = 0
}
}
설계가 약간 바뀌어서 맞지 않은 부분을 살짝 수정했습니다.
enum EnglishCodeSentenceCollectionViewCellItem {
case selected(cellViewModel : EnglishCodeBlock)
case candidate(cellViewModel : EnglishCodeBlock, isSelected : Bool)
}
enum EnglishCodeSentenceCollectionViewSectionModel {
case selectedSection(items : [EnglishCodeSentenceCollectionViewCellItem])
case candidateSection(items : [EnglishCodeSentenceCollectionViewCellItem])
}
extension EnglishCodeSentenceCollectionViewSectionModel : SectionModelType {
typealias Item = EnglishCodeSentenceCollectionViewCellItem
var items : [EnglishCodeSentenceCollectionViewCellItem] {
switch self {
case .selectedSection(items: let items):
return items.map {$0}
case .candidateSection(items: let items):
return items.map {$0}
}
}
init(original: EnglishCodeSentenceCollectionViewSectionModel, items: [Item]) {
switch original {
case .selectedSection(items: let items):
self = .selectedSection(items: items)
case .candidateSection(items: let items):
self = .selectedSection(items: items)
}
}
}
섹션을 가진 데이터는 SectionModelType 프로토콜을 준수해야 합니다.
public protocol SectionModelType {
associatedtype Item
var items: [Item] { get }
init(original: Self, items: [Item])
}
이 프로토콜을 준수한다고 생각하면 됩니다.
먼저 기존 코드블럭을 감싼 enum EnglishCodeSentenceCollectionViewCell 을 정의합니다.
해당 enum은 콜렉션뷰 각각의 셀에 필요한 정보를 담는 모델입니다.
그 셀을 배열로 하는 items를 연관 값으로 가진 EnglishCodeSentenceCollectionViewSectionModel 을 정의합니다.
해당 enum은 dataSource를 만들 때 사용할 것입니다.
class EnglishCodeSentenceViewModel : CodeSentence {
var selectedCodeBlocks: Variable<[EnglishCodeSentenceCollectionViewCellItem]>
var candidateCodeBlocks: Variable<[EnglishCodeSentenceCollectionViewCellItem]>
var answerCodeBlocks: [EnglishCodeBlock]
private let disposeBag = DisposeBag()
init(answerCodeBlocks : [EnglishCodeBlock], candidateCodeBlocks : [EnglishCodeBlock]){
self.answerCodeBlocks = answerCodeBlocks
self.candidateCodeBlocks = Variable(candidateCodeBlocks.map { EnglishCodeSentenceCollectionViewCellItem.selected(cellViewModel: $0) })
self.selectedCodeBlocks = Variable(EnglishCodeSentenceViewModel.setUpExampleEnglishCodeBlocks().map {
EnglishCodeSentenceCollectionViewCellItem.selected(cellViewModel: $0)
})
}
convenience init(answerCodeBlocks : [EnglishCodeBlock], candidateCodeBlocks : [EnglishCodeBlock], itemSelected : Driver<IndexPath>){
self.init(answerCodeBlocks: answerCodeBlocks, candidateCodeBlocks: candidateCodeBlocks)
itemSelected.drive(onNext: { [unowned self ] indexPath in
if (indexPath.section == 0){
self.selectedCodeBlocks.value.remove(at: indexPath.item)
}else {
self.candidateCodeBlocks.value.remove(at: indexPath.item)
}
}).disposed(by: disposeBag)
}
static func setUpExampleEnglishCodeBlocks() -> [EnglishCodeBlock]{
return [EnglishCodeBlock.init(name: "I"), EnglishCodeBlock.init(name: "am") ,
EnglishCodeBlock.init(name: "a"), EnglishCodeBlock.init(name: "boy")]
}
}
뷰모델 파트도 바뀌었습니다.
일단 Variable로 두 섹션에 해당하는 selectedCodeBlocks와 candiateCodeBlocks가 정의되었습니다.
그에 따라서 [EnglishCodeBlock] 인자를 적절히 Variable<[EnglishCodeSentenceCollectionViewCellItem]>로 변환되도록 매핑해 줍니다.
class EnglishCodeViewController : UIViewController, UICollectionViewDelegateFlowLayout {
lazy var collectionView : UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .white
return cv
}()
private let cellIdentifier = "Cell"
private let disposeBag = DisposeBag()
var viewModel : EnglishCodeSentenceViewModel!
let dataSource = RxCollectionViewSectionedReloadDataSource<EnglishCodeSentenceCollectionViewSectionModel>(configureCell: { (dataSource, collectionView, indexPath, item) -> UICollectionViewCell in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
guard let englishCollectionViewCell : EnglishCodeCollectionViewCell = cell as? EnglishCodeCollectionViewCell else{
return cell
}
switch dataSource[indexPath] {
case let .selected(viewModel):
englishCollectionViewCell.codeLabel.text = viewModel.codeBlockName
case let .candidate(viewModel, isSelected):
englishCollectionViewCell.codeLabel.text = viewModel.codeBlockName
}
return englishCollectionViewCell
}
)
override func viewDidLoad() {
setUpViews()
setUpViewModels()
setUpCollectionViewBinding()
}
func setUpViewModels(){
viewModel = EnglishCodeSentenceViewModel.init(answerCodeBlocks: [], candidateCodeBlocks: EnglishCodeSentenceViewModel.setUpExampleEnglishCodeBlocks(), itemSelected: collectionView.rx.itemSelected.asDriver())
}
func setUpViews(){
self.view.backgroundColor = UIColor.white
self.view.addSubview(collectionView)
collectionView.snp.makeConstraints { (make) in
make.bottom.top.leading.trailing.equalTo(self.view)
}
collectionView.rx.setDelegate(self).disposed(by: disposeBag)
collectionView.register(EnglishCodeCollectionViewCell.self, forCellWithReuseIdentifier: cellIdentifier)
}
func setUpCollectionViewBinding(){
let selected = viewModel.selectedCodeBlocks.asObservable().map { (items) -> EnglishCodeSentenceCollectionViewSectionModel in
return .selectedSection(items: items)
}
let candidate = viewModel.candidateCodeBlocks.asObservable().map { (items) -> EnglishCodeSentenceCollectionViewSectionModel in
return .candidateSection(items: items)
}
Observable.combineLatest([selected, candidate])
.bind(to: collectionView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
}
}
뷰 쪽의 바인딩이 좀 복잡해졌습니다.
뷰모델에서 두 옵저버를 SectionModel형태로 매핑하고, 그것들 둘을 컴바인하면 Observable[SectionModel] 형태가 됩니다.
그 결과값을 콜렉션뷰에 바인딩합니다.
인터넷에 있는 예제와 깃허브의 예제를 조합해 어떻게든 짜 보았습니다만,
패러다임이 달라서 그런지 짜기가 쉽지가 않습니다.
콜렉션뷰에서 셀을 옮기거나 셀의 프레임을 계산할 때 등 복잡한 경우를 적절하게 구현하려면 더 공부를 해야할 것 같습니다.
반응형'iOS > mvvm + RxSwift 스터디' 카테고리의 다른 글
RxSwift에서 combineLatest 와 withLatestFrom 차이 이해하기 (0) 2019.03.03 RxSwift 강좌 (0) 2018.12.07 RxSwift 슬랙 가입 (0) 2018.12.06 rxSwift + mvvm 구조로 콜렉션뷰 설계하기 (3) (0) 2018.12.05 rxSwift + mvvm 구조로 콜렉션뷰 설계하기 (0) 2018.12.04