RxDataSourcesを利用してeditActionsForRowAt機能を実現する


RxSwiftを導入したあるアプリの制作中、既存のUITableViewDelegateのeditActionsForRowAtを利用したカスタムアクションボタンような機能を実現しようすることをメモしておきます。

既存のUITableViewDelegateでの実装は大抵以下の様子:

EventListViewController.swift

class EventListViewController: UITableViewController {
// MARK: - View LifeCircle
    override func viewDidLoad() {
        super.viewDidLoad()
        // table cell を初期化
        self.tableView.register(UINib(resource: R.nib.eventCell), forCellReuseIdentifier: R.reuseIdentifier.eventCell.identifier)
      self.tableView.delegate = self
        self.tableView.dataSource = self
 }
// ... ...
// MARK: - UITableViewDataSource
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return mockEventList.count
    }
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: R.reuseIdentifier.eventCell, for: indexPath) else {
            return UITableViewCell()
        }
        let item = self.mockEventList[indexPath.row]
        cell.configure(viewModel: item)
        return cell
    }
    override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        // TODO:  check edit conditions
        if checkEdit {
            return false
        }
        return true
    }

// MARK: - UITableViewDelegate
    override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
        // ここのカスタマイズしたいアクションをを登録する
        let action1 = UITableViewRowAction(style: .normal, title: "edit", handler: { action, _ in
                print("edit action")
         })
        let action2 = UITableViewRowAction(style: .normal, title: "delete", handler: { action, _ in
            print(" delete action")
        })

        action1.backgroundColor = UIColor.red
        action2.backgroundColor = UIColor.clear
        return [action1, action2]
    }
}

通常なら、上記のデリゲートを利用することで実現できるが、
RxSwiftを利用したリアクティブ開発では、RxCocoaから拡張されたライブラリRxDataSourcesを利用して上記機能を実現させます。

1) まずは RxDataSourcesのSectionModelTypeプロトーコルに準拠する構造体を定義する

EventSectionItem.swift
import Foundation
import RxDataSources

enum EventSection {
    case events(title: String, items: [EventSectionItem])
}

enum EventSectionItem {
    case eventItem(cellViewModel: EventCellViewModel)
}

extension EventSection: SectionModelType {

    var title: String {
        switch self {
            case .events(let title, _): return title
        }
    }

    var items: [EventSectionItem] {
        switch  self {
            case .events(_, let items): return items.map {$0}
        }
    }

    init(original: EventSection, items: [EventSectionItem]) {
        switch original {
            case .events(let title, let items): self = .events(title: title, items: items)
        }
    }
}

2) Rx関連初期登録を行う

EventListViewController.swift
import RxSwift
import RxCocoa
import RxDataSources // <- 今度利用するライブラリ

class EventListViewController: UITableViewController {
    // DisposeBagの登録
    let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
        //... ...
        // Cell 初期登録
        let reuseIdentifier = R.reuseIdentifier.eventCell.identifier
        self.tableView.register(UINib(resource: R.nib.eventCell), forCellReuseIdentifier: reuseIdentifier)
        // デリケードを登録 (dataSourceは以下RxDataSourcesを利用して登録するためnilで初期化)
        self.tableView.dataSource = nil
        self.tableView.rx.setDelegate(self).disposed(by: disposeBag)
    }
}

3) 引き続きRxのdataSourceの初期処理と登録

EventListViewController.swift
class EventListViewController: UITableViewController {
    let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
     // dataSourceを定義
        let dataSource = RxTableViewSectionedReloadDataSource<EventSection>(configureCell: { dataSource, tableView, indexPath, item in
            switch item {
                case .eventItem(let cellViewModel):
                    let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as? EventCell
//                    cell.bind(to: cellViewModel)
                    cell?.configure(viewModel: cellViewModel)
                    return cell ?? UITableViewCell()
            }
        }, titleForHeaderInSection: { dataSource, index in
            let section = dataSource[index]
            return section.title
        }, canEditRowAtIndexPath: {_, _ in
            return true // 一旦can Editできるよう trueを返す
        })
    }
}

4) 最後は, 通常と同じくUITableViewDelegateのeditActionsForRowAtメソットをoverrideする

EventListViewController.swift

class EventListViewController: UITableViewController {
    // ... ...

    // MARK: - UITableViewDelegate
    override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
        guard let curUser = UserService.default.getUser() else {
            return [] // not logged
        }
        // canEditableの条件判断をここで実現する
        // 実現方法は以下URLの記事を参照ください
        // https://qiita.com/ENIX/items/b0871a5961b3a27c32fa
        if checkEdit { 
            return[]
        }

        // カスタマイズしたいアクションをを同じくここで登録する
        let action1 = UITableViewRowAction(style: .normal, title: "edit", handler: { action, _ in
                print("edit action")
         })
        let action2 = UITableViewRowAction(style: .normal, title: "delete", handler: { action, _ in
            print(" delete action")
        })

        action1.backgroundColor = UIColor.red
        action2.backgroundColor = UIColor.clear
        return [action1, action2]
    }
}

以上、大変お疲れ様でした。

もしもっと良い実現案がございましたら、共有いただけると大変嬉しいですね。

iOS、Androidアプリの制作なら、[email protected]まで、
法人並みに、信頼且つ満足できる製品を納品いたします