この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
こんにちは。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
を表示する実装を紹介しました。同じような実装をやろうとしている方の参考になれば幸いです。
サンプルプロジェクトは以下のリポジトリで公開していますのでこちらも参考にしてください。