次の要件を満たすTableViewを実装します。
- セクションヘッダーをタップすると、セクション下にあるすべての項目を非表示にする(収束)
- 収束状態のセクションヘッダーをタップすると、セクション下にあるすべての項目を表示する(展開)
セクションデータクラスの実装
各セクションの表示状態を決定するにあたり、収束/展開どちらの状態であるかを知る必要があります。
収束の状態 isConvergence
をプロパティに持つ SectionData
クラスを実装します。
class SectionData: NSObject {
var isConvergence: Bool = false
var rows = [String]()
}
プロパティ rows
はセクションに属するデータソースのリストです。
サンプルではString型のリストですが、用途に合わせて変更してください。
タップ可能なセクションヘッダーの実装
タップに反応するヘッダーを実装します。
タップイベントをUITableViewの実装先へ通知するまでがヘッダーの役割とします。
protocol TableHeaderViewDelegate: class {
/// 収束状態の変更要求
func changeConvergenceState(view: TableHeaderView, section: Int)
}
class TableHeaderView: UILabel {
fileprivate var section: Int = 0
weak var delegate: TableHeaderViewDelegate? = nil
convenience init(section: Int) {
self.init(frame: CGRect.zero)
self.section = section
self._init()
}
}
// MARK: - fileprivate functions
fileprivate extension TableHeaderView {
func _init() {
self.isUserInteractionEnabled = true
self.textAlignment = .center
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleView_Tap(sender:))))
}
@objc func handleView_Tap(sender: UITapGestureRecognizer) {
self.delegate?.changeConvergenceState(view: self, section: self.section)
}
}
サンプルでは必要最小限、セクションの情報を示す為のUILabelに機能を追加しています。
UILabelにタップイベントを追加し、 TableHeaderViewDelegate
の changeConvergenceState()
メソッドをコールします。
UILabelでは isUserInteractionEnabled
に true
を設定しないとタップイベントが反応しないことに注意してください。
UITableViewをメンバに持つ、UIViewControllerの実装
UIViewControllerに今回の要件を実装します。
UIViewControllerにUITableViewを追加し、 UITableViewDataSource
と UITableViewDelegate
を関連づけます。
class ViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
fileprivate lazy var sections: [SectionData] = {
var sections = [SectionData]()
for section in 0...4 {
let data = SectionData()
for row in 0...4 {
data.rows.append("\(section.description): \(row.description)")
}
sections.append(data)
}
return sections
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
// MARK: - UITableViewDataSource
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return self.sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionData = self.sections[section]
// 実装ポイント
// セクションが収束している場合(isConvergence)、0を返します
return sectionData.isConvergence ? 0 : sectionData.rows.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = self.tableView.dequeueReusableCell(withIdentifier: "cell")
if cell == nil {
cell = UITableViewCell(style: .default, reuseIdentifier: "cell")
}
cell?.textLabel?.text = self.sections[indexPath.section].rows[indexPath.row]
return cell!
}
}
// MARK: - UITableViewDelegate
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 60.0
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = TableHeaderView(section: section)
view.delegate = self
view.text = "section \(section.description) header"
view.backgroundColor = #colorLiteral(red: 0.721568644, green: 0.8862745166, blue: 0.5921568871, alpha: 1)
return view
}
}
// MARK: - TableHeaderViewDelegate
extension ViewController: TableHeaderViewDelegate {
func changeConvergenceState(view: TableHeaderView, section: Int) {
// 実装ポイント
// セクションがタップされた際、該当するセクションの収束状態を反転し、リロードします
self.sections[section].isConvergence = !self.sections[section].isConvergence
self.tableView.reloadSections(IndexSet([section]), with: .automatic)
}
}
sections
プロパティにはテーブルに表示するすべてのデータが入ります。
セクションの行数を返すメソッド tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
では、収束の状態により返す値を変えています。
収束している場合は0を、展開している場合は実データの数を返します。
セクションヘッダーを返すメソッド 'tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?' では、先ほど実装した TableHeaderView
を返します。
その際 delegate
を関連づけることで、 changeConvergenceState(view: TableHeaderView, section: Int)
がコールされるようになります。
セクションヘッダーがタップされた際にコールされるメソッド changeConvergenceState(view: TableHeaderView, section: Int)
では、該当するセクションの収束状態を反転し、リロードを行います。
リロードを行うことで tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
がコールされ、収束状態にあった表示に更新されます。
データ操作を行う際の注意事項
セクションの収束状態を表現するため、 tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
では実データに合わない数(0)を返しています。
その為、無条件にTableViewに対して insert
や move
などを行うと例外が発生しますので注意が必要です。
実装の際は特に収束状態のセクションに気をつけてください。
追加の場合
セクションが収束している場合、tableViewに insert
は行わない
更新の場合
セクションが収束している場合、tableViewに reload
は行わない
削除の場合
セクションが収束している場合、tableViewに delete
は行わない
移動の場合
移動元セクションが収束している、移動先セクションが収束していない場合
moveRow
は使わない。移動先セクションに対して insert
を行う
移動元セクションが収束していない、移動先セクションが収束している場合
moveRow
は使わない。移動元セクションに対して delete
を行う
移動元/移動先共に収束している
tableViewに対する操作は行わない
サンプルコード
サンプルをGitHubに公開しています。
https://github.com/ICTFractal/ConvergenceSectionTableViewSample/tree/master