본문 바로가기
iOS/Swift

RxSwift UITableView (2) - More Section

by DnaJ 2019. 7. 20.
반응형

RxSwift UITableView (2) - More Section

RxSwift UITableView 에서 여러 section을 사용하는 방법에 대한 포스팅이다.

이전 포스팅에서는 한가지 Tyep의 Cell과 여러가지 Type의 Cell을 RxSwift로 구현했다.

2019/07/15 - [iOS/Swift] - RxSwift UITableView (1) - One Section, One Cell, More Cell

 

RxSwift UITableView (1) - One Section, One Cell, More Cell

RxSwift UITableView (1) - One Section, One Cell, More Cell UITableView관한 포스팅이다. UITableView로 MVVM패턴을 많이 적용하는 예제들이 많다. 하지만 이번 포스팅은 MVVM패턴은 1도 사용하지 않았다. UITa..

myseong.tistory.com

여러 Section을 사용하기 위해서는 RxDataSourcesPodfile에 추가해야 한다.

 

 

https://play.google.com/store/apps/details?id=com.danchoo.tagalbum&hl=ko

 

태그앨범 - Google Play 앱

사진과 앨범을 태그로 관리하세요. 결혼식, 팬클럽, 동호회등 원하는 카테고리를 만들어 정리해보세요. 사진에 태그를 설정하여 손쉽게 찾아보세요!

play.google.com

 

 

RxSwift 5가 나왔다. 

RxSwift 5에 맞춰서 RxDataSources가 4.0까지 나왔다.

하지만 RxRealmDataSources가 아직 RxSwift 5에 마이그레이션이 안됐다고 한다.... 조만간 되겠지....

기존 프로젝트에 RxRealmDataSource가 포함되어 있는 바람에....ㅎㅎ

아래와 같은 설정으로 사용하고 있다.

 

Podfile

일부만 가져왔다.

 

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!

target 'ProjectName' do
  
  #RxSwift
  pod 'RxSwift',    '~> 4.0'
  pod 'RxCocoa',    '~> 4.0'
  pod 'RxDataSources', '~> 3.0'
  pod 'RxRealmDataSources'
  
end 

 

아래있는 코드를 보면 SampleSectionTableViewController 을 만들어서 필요한 객체를 선언했다. (전체코드는 맨 아래에 있습니다.)

extension을 사용해서 4부분으로 나누었다.

1. property 선언

2. 기본 함수 및 셋팅

3. UITableViewDelegate

4. UITableViewDataSource

 

1. property 선언

특히 중요한 것은 맨 아래줄에 있는 dataSource이다.

 

class SampleSectionTableViewController: UIViewController {

    @IBOutlet var tableView: UITableView!
    
    private let disposeBag = DisposeBag()
    
    private var tableViewItems = BehaviorRelay(value: [SampleSectionItem]())
    
    private var dataSource: RxTableViewSectionedReloadDataSource<SampleSectionItem>!
}

 

private var dataSource: RxTableViewSectionedReloadDataSource<SampleSectionItem>!

dataSource를 정의했는데 viewDidLoad가 끝나기 전에 초기화를 했다. <- '!'를 마지막에 달고 있기 때문에

dataSource를 사용할때마다 '?'를 붙이기 싫어서 '!'를 붙여버렸다..........

(SampleSectionItem은 SectionModelType을 상속받은 사용자가 생성한 Class)

 

class RxTableViewSectionedReloadDataSource<S: SectionModelType>

 

open class RxTableViewSectionedReloadDataSource<S: SectionModelType>
    : TableViewSectionedDataSource<S>
    , RxTableViewDataSourceType {
    public typealias Element = [S]

    open func tableView(_ tableView: UITableView, observedEvent: Event<Element>) {
        Binder(self) { dataSource, element in
            #if DEBUG
                self._dataSourceBound = true
            #endif
            dataSource.setSections(element)
            tableView.reloadData()
        }.on(observedEvent)
    }
}

RxTableViewSectionedReloadDataSource는 위와같이  SectionModelType을 지정해줘야한다.

위의 코드에서 사용한 SampleSectionItem은 SectionModelType을 상속받은 사용자가 생성한 Class이다.

extension에 SectionModelType을 상속 받아도 문제없이 사용가능하다.

 

https://smartstore.naver.com/happysiso

 

해피시소마켓 : 네이버쇼핑 스마트스토어

SISO

smartstore.naver.com

 

 

SampleSectionItem을 살펴보자

SectionTypeenum으로 가지고 있고

items 라는 배열인 변수를 가지고 있다.

또한, extensionSectionModelType을 상속받고

typealias Item을 정의한다.

 

import RxDataSources

struct SampleSectionItem {
    
    public enum SectionType: Int {
        case today
        case week
        case none
    }
    
    var sectionType: SectionType
    var items: [String]
}

extension SampleSectionItem: SectionModelType {
    typealias Item = String
    
    init(original: SampleSectionItem, items: [Item]) {
        self = original
        self.items = items
    }
}

enum SectionType은 section을 알기쉽게(?) 나누기 위해서 선언했다.

SectinoModelType을 상속받게 되면 items 라는 배열인 변수를 반드시 선언해줘야 한다.

배열의 Type은 extension에 Item과 같은 Type으로 선언해야 한다.

 

Item은 typealias로 선언이 되어있어서 특정 Type을 지정해 줄수 있다.

typealias 

alias

1. …라는 가명으로 알려진, 일명 …라 불리는
2. (특히 범죄자의) 가명
3. 에일리어스(파일인터넷 주소 등에 쓰는 가명)

 

Type에 붙이는 별칭이다.

자세한 것은 쉽게 포스팅해놓은 곳이 있어 퍼왔다.......

 

https://ginjo.tistory.com/20

 

[Swift] typealias

typealias 기존에 선언되어있는 유형에 새로운 유형의 별칭을 사용함으로써 코드를 더 읽기 쉽도록, 이해하기 쉽도록 명확하게 만드는문법입니다. Swift 에서는 typealias 를 대부분의 유형에 사용이 가능하고 크..

ginjo.tistory.com

 

SampleSectionItem은 SectionModelType을 상속 받고있다,

SectionModelType은 프로토콜로 아래와 같이 정의가 되어있다.

import Foundation

public protocol SectionModelType {
    associatedtype Item

    var items: [Item] { get }

    init(original: Self, items: [Item])
}

 

associatedtype

associated : 연상하다, 결부짓다

 

자세한 것은 쉽게 포스팅해놓은 곳이 있어 퍼왔다.......

https://zeddios.tistory.com/382

 

iOS ) Associated Type

안녕하세요 :) Zedd입니다. 오늘은...Associated Type! Associated Type Associated Type.....처음 들으면 이게 뭐지???싶죠. 관련된 타입?..이게 뭐야.. 이 Associated Type은 프로토콜에서 사용된답니다. 프로토..

zeddios.tistory.com

 

2. 기본 함수 및 셋팅

func viewDidLoad

 -  resistCell, initData, delegate셋팅, initDataSource 를 해주고 있다.

 initDataSource는 다른 extension에 있다.

 - delegate는 rx를 이용해서 set을 해준다.

 

func initData

 - tableView에 들어갈 Item을 초기화 해주고 있다.

 - 임의의 데이터를 셋팅해 주고 있다.

 

func registCell

 - UILabel만 있는 Cell이다.

 - class SampleTableViewCell : UITableViewCell { ... }

// 기본 함수 and 셋팅
extension SampleSectionTableViewController {
    override func viewDidLoad() {
        super.viewDidLoad()      
        
        registCell()
        initData()
        
        tableView.rx
            .setDelegate(self)
            .disposed(by: disposeBag)
        
        initDataSource()
        
        // Do any additional setup after loading the view.
    }

    private func initData() {
        let today = ["today0", "today1", "today2", "today3", "today4", "today5"]
        let week = ["week0", "week1", "week2", "week3", "week4", "week5"]
        
        let todaySection = SampleSectionItem(sectionType: SampleSectionItem.SectionType.today, items: today)
        let weekSection = SampleSectionItem(sectionType: SampleSectionItem.SectionType.week, items: week)
        
        let items = [todaySection, weekSection]
        self.tableViewItems.accept(items)
    }
    
    private func registCell() {
	let nibName = "SampleTableViewCell"
        let nib = UINib(nibName: nibName, bundle: nil)
        tableView.register(nib, forCellReuseIdentifier: nibName)
    }
}

 

3. UITableViewDelegate

진짜 별거없다. 기존에 사용하던대로 해주면 되다.

// UITableViewDelegate
extension SampleSectionTableViewController: UITableViewDelegate  {
    public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let cell = self.tableView.cellForRow(at: indexPath) as? SampleTableViewCell
        guard cell != nil else {
            return
        }
        
        print("didSelectRowAt : + \(String(describing: cell?.label.text))")
        
        
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return UITableView.automaticDimension
    }
}

 

4. UITableViewDataSource

 func initDataSource

  - 개인적으로 한개의 함수안에 코드가 길게 늘어져 있는것을 싫어해서 Cell을 다른 function (getTableViewCell) 에서 생성해서 받아오도록 만들었다.

  - header 와 footer 

  - item bind 

self.tableViewItems

            .asObservable()

            .bind(to: tableView.rx.items(dataSource: self.dataSource))

            .disposed(by: disposeBag)

 

이전 포스팅과 다르게 items에 dataSource를 셋팅해준다.

 .bind(to: tableView.rx.items(dataSource: self.dataSource))

 

func getTableViewCell

 - 항상 많이 길어지는 함수다.

   . 코드를 분리를 하지 않아 많게는 몇백라인까지 가는것을 보았다.

   . 되도록이면 함수를 분리해서 가독성이 좋도록 구성하자........

   . 한개의 함수는 되도록 한가지 일만!! 뎁스는 되도록이면 깊지 않게!! 로직은 단순하게!!

 - smaple 코드 이기때문에 다른 Section이지만 한가지 Cell로만 구성했다. 

 - SampleSectionItem.getSctionType(indexPath.section)

   . SampleSectionItem에 구현했다. (포스팅 제일 밑 전체 코드에 포함)

   . enum의 rawValue를 가져오기 위해서 만들었다. (sectino이 int Type이므로)

   . 다른 방법이 딱히 떠오르지 않아 만들었는데 더 좋은 방법이 있는지 확인 해봐야 한다.

 

// UITableViewDataSource
extension SampleSectionTableViewController  {
    private func initDataSource() {
        self.dataSource = RxTableViewSectionedReloadDataSource<SampleSectionItem> (configureCell: { [weak self] (dataSource, tableView, indexPath, element) -> UITableViewCell in
            
            let cell: UITableViewCell = self?.getTableViewCell(tableView, indexPath: indexPath, item: element) ?? UITableViewCell()
            return cell
        })
        
        self.dataSource.titleForHeaderInSection = { dataSource, index in
            return "header" + String(index)
        }
        
        self.dataSource.titleForFooterInSection = { dataSource, index in
            return "footer" + String(index)
        }
            
        self.tableViewItems
            .asObservable()
            .bind(to: tableView.rx.items(dataSource: self.dataSource))
            .disposed(by: disposeBag)
        
        self.tableView.dataSource = self.dataSource
    }
    
    private func getTableViewCell(_ tableView: UITableView, indexPath: IndexPath, item: String) -> UITableViewCell {
        var cell: SampleTableViewCell
        
        let section: SampleSectionItem.SectionType = SampleSectionItem.getSctionType(indexPath.section)
        
        switch section {
        case .today:
            cell = tableView.dequeueReusableCell(withIdentifier: "SampleTableViewCell", for: indexPath) as! SampleTableViewCell
            break
            
        case .week:
            cell = tableView.dequeueReusableCell(withIdentifier: "SampleTableViewCell", for: indexPath) as! SampleTableViewCell
            break
            
        case .none:
            cell = tableView.dequeueReusableCell(withIdentifier: "SampleTableViewCell", for: indexPath) as! SampleTableViewCell
            break
        }
        
        cell.label.text = item
        return cell
    }
}

 

 

전체코드

import RxDataSources

struct SampleSectionItem {
    
    public enum SectionType: Int {
        case today
        case week
        case none
    }
    
    var sectionType: SectionType
    var items: [String]
    
    static func getSctionType(_ rowValue: Int) -> SectionType {
        var type: SectionType
        
        switch rowValue {
        case SectionType.today.rawValue:
            type = SectionType.today
            break
            
        case SectionType.week.rawValue:
            type = SectionType.week
            break
            
        default:
            type = SectionType.none
            break
        }
        
        return type
    }
}

extension SampleSectionItem: SectionModelType {
    typealias Item = String
    
    init(original: SampleSectionItem, items: [Item]) {
        self = original
        self.items = items
    }
}

 

import UIKit
import RxCocoa
import RxSwift
import RxDataSources

// property 선언
class SampleSectionTableViewController: UIViewController {

    @IBOutlet var tableView: UITableView!
    
    private let disposeBag = DisposeBag()
    
    private var tableViewItems = BehaviorRelay(value: [SampleSectionItem]())
    
    private var dataSource: RxTableViewSectionedReloadDataSource<SampleSectionItem>!
    
}

// 기본 함수 and 셋팅
extension SampleSectionTableViewController {
    override func viewDidLoad() {
        super.viewDidLoad()      
        
        registCell()
        initData()
        
        tableView.rx
            .setDelegate(self)
            .disposed(by: disposeBag)
        
        initDataSource()
        
        // Do any additional setup after loading the view.
    }

    private func initData() {
        let today = ["today0", "today1", "today2", "today3", "today4", "today5"]
        let week = ["week0", "week1", "week2", "week3", "week4", "week5"]
        
        let todaySection = SampleSectionItem(sectionType: SampleSectionItem.SectionType.today, items: today)
        let weekSection = SampleSectionItem(sectionType: SampleSectionItem.SectionType.week, items: week)
        
        let items = [todaySection, weekSection]
        self.tableViewItems.accept(items)
    }
    
    private func registCell() {
  	    let nibName = "SampleTableViewCell"
        let nib = UINib(nibName: nibName, bundle: nil)
        tableView.register(nib, forCellReuseIdentifier: nibName)
    }
}

// UITableViewDelegate
extension SampleSectionTableViewController: UITableViewDelegate  {
    public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let cell = self.tableView.cellForRow(at: indexPath) as? SampleTableViewCell
        guard cell != nil else {
            return
        }
        
        print("didSelectRowAt : + \(String(describing: cell?.label.text))")
        
        
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return UITableView.automaticDimension
    }
}

// UITableViewDataSource
extension SampleSectionTableViewController  {
    private func initDataSource() {
        self.dataSource = RxTableViewSectionedReloadDataSource<SampleSectionItem> (configureCell: { [weak self] (dataSource, tableView, indexPath, element) -> UITableViewCell in
            
            let cell: UITableViewCell = self?.getTableViewCell(tableView, indexPath: indexPath, item: element) ?? UITableViewCell()
            return cell
        })
        
        self.dataSource.titleForHeaderInSection = { dataSource, index in
            return "header" + String(index)
        }
        
        self.dataSource.titleForFooterInSection = { dataSource, index in
            return "footer" + String(index)
        }
            
        self.tableViewItems
            .asObservable()
            .bind(to: tableView.rx.items(dataSource: self.dataSource))
            .disposed(by: disposeBag)
        
        self.tableView.dataSource = self.dataSource
    }
    
    private func getTableViewCell(_ tableView: UITableView, indexPath: IndexPath, item: String) -> UITableViewCell {
        var cell: SampleTableViewCell
        
        let section: SampleSectionItem.SectionType = SampleSectionItem.getSctionType(indexPath.section)
        
        switch section {
        case .today:
            cell = tableView.dequeueReusableCell(withIdentifier: "SampleTableViewCell", for: indexPath) as! SampleTableViewCell
            break
            
        case .week:
            cell = tableView.dequeueReusableCell(withIdentifier: "SampleTableViewCell", for: indexPath) as! SampleTableViewCell
            break
            
        case .none:
            cell = tableView.dequeueReusableCell(withIdentifier: "SampleTableViewCell", for: indexPath) as! SampleTableViewCell
            break
        }
        
        cell.label.text = item
        return cell
    }
}
반응형

댓글