[iOS] UITableViewにUIDatePickerをインライン表示する
はじめに
こんにちは。CX事業本部の平屋です。
本記事では、以下のスクショのような、UITableView
にUIDatePicker
をインライン表示する実装を紹介します。
iOS標準のカレンダーアプリなどで採用されているUIで、UIDatePicker
の真上のセルをタップすると、UIDatePicker
の表示/非表示を切り替えられます。
検証環境
- macOS Mojave 10.15.3
- Xcode Version 11.3.1
実現方法
インラインでのUIDatePicker
表示を実現する方法はいくつかありますが、本記事では、UILabel
とUIDatePicker
を持つセルを用意し、このセルの高さを動的に変更することによって、インライン表示のUIDatePicker
の表示/非表示を切り替えられるようにします。UIDatePicker
の代わりに他のコンポーネント(UIPickerView
など)を使う場合でも同様な動きが実現できるかと思います。
サンプルプロジェクト
本記事は大まかな実装のみ解説します。実装の詳細は、以下のリポジトリで公開しているサンプルプロジェクトを参照してください。
PickerCellのレイアウトを作成する
まず、UILabel
とUIDatePicker
を持つPickerCell
のレイアウトを組んでいきます。
PickerCell
のContentViewのサブビューにContainerView
(UIView
)を追加し、さらにそのサブビューにUILabel
, UIDatePicker
を追加します。ビューの階層は以下のようになります。
- PickerCell
- ContentView
- ContainerView(UIView)
- UILabel
- UIDatePicker
- ContainerView(UIView)
- ContentView
ContainerViewに制約を追加する
ContainerView
の4辺(top, bottom, leading, trailing)に関する制約と、ContainerView
の高さの制約を追加します。(高さの制約の値は260、後述するUILabel
とUIDatePicker
の高さの和が260なので260を設定)
UILabelに制約を追加する
UILabel
の4辺(top, bottom, leading, trailing)に関する制約と、UILabel
の高さの制約を追加します。(高さの制約の値は44)
UIDatePickerに制約を追加する
UIDatePicker
の2辺(leading, trailing)に関する制約と、UIDatePicker
の高さの制約を追加します。(高さの制約の値は216)
UILabel
のbottomとUIDatePicker
との間の制約は追加しましたが、UIDatePicker
のbottomに関する制約は追加していません。後述の実装によってContainerView
の高さをUILabel
の高さと等しくすると、UIDatePicker
を隠すことができます。
PickerCellを実装する
続いてPickerCell
にコードを追加していきます。
アウトレットを繋ぐ
ContainerView
の高さの制約、UIDatePicker
、UILabel
をPickerCell
にアウトレット接続します。
import UIKit class PickerCell: UITableViewCell { @IBOutlet weak var containerViewHeight: NSLayoutConstraint! @IBOutlet weak var datePicker: UIDatePicker! @IBOutlet weak var label: UILabel! // ... }
PickerCell生成時の処理を実装する
UIDatePicker
とUILabel
のセットアップを行う実装を追加します。
Pickerはデフォルトで非表示にしたいのでPickerを非表示にし、ContainerView
の高さの制約をPicker非表示時用の値(compressedHeight
)にします。
class PickerCell: UITableViewCell { // ... override func awakeFromNib() { super.awakeFromNib() prepare() } @objc func datePickerValueDidChange(sender: UIDatePicker) { label.text = PickerCell.formatter.string(from: sender.date) } // ... } private extension PickerCell { static let compressedHeight: CGFloat = 44 static let expandedHeight: CGFloat = 260 // ... static let formatter: DateFormatter = // ... func prepare() { // Picker非表示時用の値を設定 containerViewHeight.constant = PickerCell.compressedHeight let now = Date() datePicker.date = now datePicker.addTarget(self, action: #selector(datePickerValueDidChange), for: .valueChanged) // Pickerを非表示にする datePicker.isHidden = true datePicker.alpha = 0 label.text = PickerCell.formatter.string(from: now) } }
Pickerを表示する処理を実装する
ContainerView
の高さの制約をPicker表示時用の値(expandedHeight
)に変更し、animate(withDuration:animations:completion:)
を呼ぶ実装を追加します。
class PickerCell: UITableViewCell { // ... func showPicker() { guard datePicker.isHidden else { return } // セルの高さの制約の値を変更して、Pickerが見えるようにする containerViewHeight.constant = PickerCell.expandedHeight datePicker.isHidden = false UIView.animate(withDuration: 0.25) { self.datePicker.alpha = 1 self.layoutIfNeeded() } } // ... }
Pickerを非表示にする処理を実装する
非表示にする処理も同様に実装します。
class PickerCell: UITableViewCell { // ... func hidePicker() { guard !datePicker.isHidden else { return } // セルの高さの制約の値を変更して、Pickerが隠れるようにする containerViewHeight.constant = PickerCell.compressedHeight UIView.animate(withDuration: 0.25, animations: { self.datePicker.alpha = 0 self.layoutIfNeeded() }, completion: { _ in self.datePicker.isHidden = true }) } // ... }
ViewControllerを実装する
UITableViewDataSource
とUITableViewDelegate
のメソッドを実装します。
PickerCell
がタップされたら、performBatchUpdates(_:completion:)
を呼び、第一引数のブロック内でPickerCell
のhidePicker()
またはshowPicker()
を呼びます。
import UIKit class ViewController: UITableViewController { private var showingDatePicker = false private var pickerCell: PickerCell? // ... } extension ViewController { override func numberOfSections(in tableView: UITableView) -> Int { // ... } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // ... } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // ... } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) guard indexPath.section == 1 else { return } // PickerCellを更新する tableView.performBatchUpdates({ // Pickerを表示する(非表示にする)メソッドを呼ぶ if self.showingDatePicker { pickerCell?.hidePicker() } else { pickerCell?.showPicker() } }, completion: { _ in self.showingDatePicker.toggle() }) } // ... }
動作確認
Pickerはデフォルト非表示で、PickerCellをタップすると表示/非表示がアニメーション付きで切り替わります。
さいごに
本記事では、UITableView
にインラインでUIDatePicker
を表示する実装を紹介しました。同じような実装をやろうとしている方の参考になれば幸いです。
サンプルプロジェクトは以下のリポジトリで公開していますのでこちらも参考にしてください。