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
여러 Section을 사용하기 위해서는 RxDataSources를 Podfile에 추가해야 한다.
https://play.google.com/store/apps/details?id=com.danchoo.tagalbum&hl=ko
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
SampleSectionItem을 살펴보자
SectionType을 enum으로 가지고 있고
items 라는 배열인 변수를 가지고 있다.
또한, extension에 SectionModelType을 상속받고
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에 붙이는 별칭이다.
자세한 것은 쉽게 포스팅해놓은 곳이 있어 퍼왔다.......
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
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
}
}
'iOS > Swift' 카테고리의 다른 글
RxSwift Realm(3) - realm변경 감지 (Observable.changeset) (0) | 2019.08.03 |
---|---|
RxSwift UITableView (1) - One Section, One Cell, More Cell (1) | 2019.07.15 |
RxSwift UITextField (0) | 2019.06.01 |
RxSwift property observe (BehaviorRelay, Variable) (0) | 2019.05.25 |
RxSwift 객체 구독하기 (Observable) (0) | 2019.05.18 |
댓글