セクション単位で折りたたむことのできるUITableViewを、ネットの情報をいろいろ調べながら作成していたのですが、どれもいろいろ気になるところがあって自分なりの実装をしてみました。気になるところもあり完成版ではないのですが、iOSの得意な人に指摘してもらえれば嬉しいです。
完成イメージ
ヘッダーのタップイベント取得
UITableViewは、ヘッダー部分をタップしたときのイベントを取得する機能がありません。このイベントを取得するために、ネットでは次の方法を見つけることができました。
しかしどれも無理矢理感がありしっくりこなかったので、ヘッダー位置のセルをカスタムで作成してGestureを登録するようにしてみた。
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に変更しています
}
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:メソッドで、折りたたんでいるときには0件を返すようにしてみました
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するようになりました。
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