はじめに
梅雨前に新しい靴買ううやつ大体梅雨舐めてる、どうもこんにちはTOSHです!
アコーディオンの実装してみようって思ったけど、世の中に書いてある記事分かり難すぎん...?
ということで、きちんと、書いてみようかなと思い立ってやってみました。
実際そんなに難しくないのに、記事がわかりにくいせいで、難しく思えちゃってたので、世界一わかりやすく説明して行こうと思います!
アコーディオンとは
これです!
よく不動産やバイト系のアプリに使用されているイメージですねー
サンプルコード
これ書けば動きます。
import UIKit
struct rail {
var isShown: Bool
var railName: String
var stationArray: [String]
}
final class ViewController: UIViewController {
@IBOutlet private weak var tableView: UITableView! {
didSet {
tableView.dataSource = self
tableView.delegate = self
}
}
private let headerArray: [String] = ["山手線", "東横線", "田園都市線", "常磐線"]
private let yamanoteArray: [String] = ["渋谷", "新宿", "池袋"]
private let toyokoArray: [String] = ["自由ヶ丘", "日吉"]
private let dentoArray: [String] = ["溝の口", "二子玉川"]
private let jobanArray: [String] = ["上野"]
private lazy var courseArray = [
rail(isShown: true, railName: self.headerArray[0], stationArray: self.yamanoteArray),
rail(isShown: false, railName: self.headerArray[1], stationArray: self.toyokoArray),
rail(isShown: false, railName: self.headerArray[2], stationArray: self.dentoArray),
rail(isShown: false, railName: self.headerArray[3], stationArray: self.jobanArray)
]
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if courseArray[section].isShown {
return courseArray[section].stationArray.count
} else {
return 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
cell?.textLabel?.text = courseArray[indexPath.section].stationArray[indexPath.row]
return cell!
}
func numberOfSections(in tableView: UITableView) -> Int {
return courseArray.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return courseArray[section].railName
}
}
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UITableViewHeaderFooterView()
let gesture = UITapGestureRecognizer(target: self,
action: #selector(headertapped(sender:)))
headerView.addGestureRecognizer(gesture)
headerView.tag = section
return headerView
}
@objc func headertapped(sender: UITapGestureRecognizer) {
guard let section = sender.view?.tag else {
return
}
courseArray[section].isShown.toggle()
tableView.beginUpdates()
tableView.reloadSections([section], with: .automatic)
tableView.endUpdates()
}
}
実装の簡単流れ
- Modelを作成する
- sectionごとにリストを表示するようにする
- Headerのタップを感知する
- タップによって表示非表示を反転させる
実装説明
では、実際に説明に入ります!
Modelを作成する
まずは表示を行うためのモデルを作成しましょう!今回のケースでは、そのもでは持つべき情報は、
- 表示をされるか否か
- 路線名
- 駅名
の三つになります。
それに対応する、モデルを作成するとこちらのようになります。
struct rail {
var isShown: Bool
var railName: String
var stationArray: [String]
}
今回生成するものは、静的なモデルで良いので、こんかいはこのように、先ほどのモデルを配列にします。配列にしておくことで、後々TableViewにセットする際に、使いやすい形になると思います。
private let headerArray: [String] = ["山手線", "東横線", "田園都市線", "常磐線"]
private let yamanoteArray: [String] = ["渋谷", "新宿", "池袋"]
private let toyokoArray: [String] = ["自由ヶ丘", "日吉"]
private let dentoArray: [String] = ["溝の口", "二子玉川"]
private let jobanArray: [String] = ["上野"]
//今回使用するモデルの配列
private lazy var courseArray = [
rail(isShown: true, railName: self.headerArray[0], stationArray: self.yamanoteArray),
rail(isShown: false, railName: self.headerArray[1], stationArray: self.toyokoArray),
rail(isShown: false, railName: self.headerArray[2], stationArray: self.dentoArray),
rail(isShown: false, railName: self.headerArray[3], stationArray: self.jobanArray)
]
モデルの配列のみ、lazyをつけてあげるようにしましょう!詳しい説明はここでは、割愛させていただきます。
これらの変数は基本的にダイブからアクセスされることがないので、privateにしていますが、ここはあくまで好みで。
sectionごとにリストを表示するようにする
ここでの設定は基本的に、通常のTableViewの作成方法となんら変わりはありません。
final class ViewController: UIViewController {
//tableViewのdelegateとdataSourceをセットしましょう!
@IBOutlet private weak var tableView: UITableView! {
didSet {
tableView.dataSource = self
tableView.delegate = self
}
}
}
次に、DataSourceを設定して行きます。ここでは、主に。TableViewの中身を管理していると思うとわかりやすいのではないでしょうか?
extension ViewController: UITableViewDataSource {
//各セッションの中のcellの数をここでセットしています。
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return courseArray[section].stationArray.count
}
//ここでは、cellのなかに何が入るのかを設定しています。cellはtitleLabelを元々持っているのでそのまま使ってしまいましょう!
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
cell?.textLabel?.text = courseArray[indexPath.section].stationArray[indexPath.row]
return cell!
}
//ここではセクションの数を設定しています。今回は、路線の数だけ、セクションが必要になります。
func numberOfSections(in tableView: UITableView) -> Int {
return courseArray.count
}
//ここでは、セクションのタイトルに何を入れるかをセットしています。
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return courseArray[section].railName
}
}
ここまで、書いてコンパイルをすると、このような状態になると思います!
まだこの状態では、肝心のアコーディオンはできないので、次の項目からは、その設定をしていこうと思います!
Headerのタップを感知する
Headerのタップ有無でアコーディオン状態を切り替えるのですが、現在のままではタップを感知できていません。
ので、HeaderにUITapGestureを設定する必要があります。こちらは、UITableViewDelegateで設定をして行きます。
extension ViewController: UITableViewDelegate {
//HeaderのViewに対して、タップを感知できるようにして行きます。
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UITableViewHeaderFooterView()
//UITapGestureを定義する。Tapされた際に、headertappedを呼ぶようにしています。
let gesture = UITapGestureRecognizer(target: self,
action: #selector(headertapped(sender:)))
//ここで、実際に、HeaderViewをセットします。
headerView.addGestureRecognizer(gesture)
headerView.tag = section
return headerView
}
//タップされるとこのメソッドが呼ばれます。
@objc func headertapped(sender: UITapGestureRecognizer) {
print("タップされたよ")
}
}
タップによって表示非表示を反転させる
先ほどのフェーズで、タップは感知出来たので、次は表示、非表示を切り替えましょう
extension ViewController: UITableViewDelegate {
~~略~~
//タップされるとこのメソッドが呼ばれます。
@objc func headertapped(sender: UITapGestureRecognizer) {
//tagを持っていない場合は、guardします。
guard let section = sender.view?.tag else {
return
}
//courseArray[section].isShownの値を反転させます。
courseArray[section].isShown.toggle()
//これ以降で表示、非表示を切り替えます。
tableView.beginUpdates()
tableView.reloadSections([section], with: .automatic)
tableView.endUpdates()
}
}
これで、isShownを判定させて、再描画を呼び鵜出すことが出来たので、TableViewのDataSourceでも設定を行って行きましょう!
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//courseArray[section].isShownの値によって、表示数を変更
if courseArray[section].isShown {
return courseArray[section].stationArray.count
} else {
return 0
}
}
~~略~~
}
ちなみに、個人的には、こんな書き方の方が好きだったりします。
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return courseArray[section].isShown ? courseArray[section].stationArray.count : 0
}
これで、表示、非表示を切り替えることができました!
このようになるはずです。
まとめ
ポイントは、headerのタップをきちんと感知することかなと思います!
よかったら、スターお願いします!
https://github.com/tosh7/Accordion_Sample