セクション単位で折りたためる、アコーディオン効果付きのUITableViewの作り方

セクション単位で折りたたむことのできるUITableViewを、ネットの情報をいろいろ調べながら作成していたのですが、どれもいろいろ気になるところがあって自分なりの実装をしてみました。気になるところもあり完成版ではないのですが、iOSの得意な人に指摘してもらえれば嬉しいです。

# 完成イメージ
![ExpandTableView.gif](https://qiita-image-store.s3.amazonaws.com/0/75307/02b47ee2-0cd6-9dab-a7f6-83d7580b266d.gif)

# ヘッダーのタップイベント取得

UITableViewは、ヘッダー部分をタップしたときのイベントを取得する機能がありません。このイベントを取得するために、ネットでは次の方法を見つけることができました。

* tableView.touchesEnded:withEvent:をオーバーライドする方法 → [参考ページ](http://ponpoko1968.hatenablog.com/entry/20101003/1286087248)
* Cellを一行増やす方法 → [参考ページ](http://qiita.com/vitaro/items/bced03e8a50d3346c91a)

しかしどれも無理矢理感がありしっくりこなかったので、ヘッダー位置のセルをカスタムで作成してGestureを登録するようにしてみた。

```swift:ViewController.swift
    override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        var cell = tableView.dequeueReusableHeaderFooterViewWithIdentifier("Header") as? CustomHeaderFooterView
        if cell == nil {
            cell = CustomHeaderFooterView(reuseIdentifier: "Header")
            cell?.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "tapHeader:"))
        }
        cell!.textLabel!.text = self.sections[section].title
        cell!.section = section
        cell!.setExpanded(self.sections[section].extended)
        return cell
    }
    
    func tapHeader(gestureRecognizer: UITapGestureRecognizer) {
        guard let cell = gestureRecognizer.view as? CustomHeaderFooterView else {
            return
        }
        let extended = self.sections[cell.section].extended
        self.sections[cell.section].extended = !extended
        tableView.reloadData()  // 追記で、reloadSectionsに変更しています
    }
```

```swift:CustomHeaderFooterView.swift
import UIKit

class CustomHeaderFooterView: UITableViewHeaderFooterView {
    private var arrow = UIImageView()
    var section: Int = 0
    
    override init(reuseIdentifier: String?) {
        super.init(reuseIdentifier: reuseIdentifier)
        self.arrow.translatesAutoresizingMaskIntoConstraints = false
        self.contentView.addSubview(self.arrow)
        self.contentView.addConstraints([
            NSLayoutConstraint(item: arrow, attribute: .Trailing, relatedBy: .Equal, toItem: self.contentView, attribute: .Trailing, multiplier: 1.0, constant: -8),
            NSLayoutConstraint(item: arrow, attribute: .CenterY, relatedBy: .Equal, toItem: self.contentView, attribute: .CenterY, multiplier: 1.0, constant: 0)])
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func setExpanded(expanded: Bool) {
        arrow.image = UIImage(named: expanded ? "ArrowUp" : "ArrowDown")
    }
}
```

# セルの表示非表示

ヘッダーのイベント発生時にセルの表示非表示を制御するために、データソースからデータを削除する方法で実装しているものがありました。しかし、表示のためにデータソースそのものを編集するのはどうかなあと思ったので、tableView:numberOfRowsInSection:メソッドで、折りたたんでいるときには０件を返すようにしてみました

```swift:ViewController.swift
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
        return self.sections[section].extended ? sections[section].details.count : 0
    }
```

# 気になっているところ

* セクションをタップしたときに、スクロール位置がずれることがある
* ~~アニメーション効果が入っていない~~
* ~~セクションをタップしたときに`tableView.reloadData()'で全レコードを更新している~~

追記しました

# 追記（アニメーション効果とtableView.reloadDataについて）

tableView.reloadSectionsを使うと、いい感じにアニメーションがついて、なおかつ対象となるセクションのみreloadするようになりました。

```swift:ViewController.swift
    func tapHeader(gestureRecognizer: UITapGestureRecognizer) {
        guard let cell = gestureRecognizer.view as? CustomHeaderFooterView else {
            return
        }
        let extended = self.sections[cell.section].extended
        self.sections[cell.section].extended = !extended
        tableView.reloadSections(NSIndexSet(index: cell.section), withRowAnimation: .None)
    }
```

ソースはこちらです。
https://github.com/nakaken0629/ExpandTableViewSample