iOS14のUIDatePickerのデザインをカスタマイズする
iOS14からUIDatePickerのデザインが新しくなり、使いやすくなりました。特にUIDatePickerStyle.compact
は日付を押したらDatePickerが表示されるという実装がしやすくなってます。
ただ、DatePickerを閉じている際のデザインを標準では変更できず少し使いづらいので、見た目変えてみたいと思います。
基本的な方針
デフォルトのデザインをいじるのは将来のアップデートで崩壊する可能性が高くコードも煩雑になります。そこで、今回は元々のViewを透明にして、上に独自のViewを被せる形にします。
元々のデザインは単純で、グレーの背景色とtintColor
で表示される日付のラベルだけです。これらを見えなくするのですが、無造作にopacity = 0
にしてしまうとタップが効かなくなってカレンダーを開けなくなってしまうので、再帰的にbackgroundColor
が指定されているViewを探し出して隠します。
そして、これをlayoutSubviews
など画面描画のタイミングに呼ばれるメソッドに入れ込んでやれば、見えないけどタップしたらカレンダーが出てくるViewができます。
class CustomDatePicker: UIDatePicker {
override func layoutSubviews() {
super.layoutSubviews()
makeTransparent(view: self)
}
func makeTransparent(view: UIView) {
if view.backgroundColor != nil {
view.isHidden = true
} else {
for subview in view.subviews {
customize(view: subview)
}
}
}
}
カスタムビューを重ねる
僕の場合日付のフォーマットとフォントサイズを変更したかったので、今回はこの上にUILabel
を重ねてみます。
といっても、やることは単純で、
UILabel
を生成willMove(toSuperview:)
など、Viewが表示されるまでに呼ばれるメソッド内でaddSubview
を行うlayoutSubviews()
でframe
とtext
の更新
といった感じ。サブクラスでUIViewを追加する常套手段だと思います。
class CustomDatePicker: UIDatePicker {
let label = UILabel()
func updateLabel() {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ja_JP")
formatter.setLocalizedDateFormatFromTemplate("MMM d yyyy hh:mm")
label.text = formatter.string(from: date)
}
override func willMove(toSuperview newSuperview: UIView?) {
super.willMove(toSuperview: newSuperview)
addSubview(label)
}
func makeTransparent(view: UIView) {
if view === label { return }
if view.backgroundColor != nil {
view.isHidden = true
} else {
for subview in view.subviews {
customize(view: subview)
}
}
}
override func layoutSubviews() {
super.layoutSubviews()
makeTransparent(view: self)
label.frame = CGRect(origin: .zero, size: frame.size)
updateLabel()
}
}
ちなみに、makeTransparent
をlayoutSubviews
で呼ぶのは、didMoveToSuperview()
で1回呼んだ程度じゃ透明になってくれなかったから。
でも、いくら再帰関数を使っているとはいえ、対象は小さなUIDatePicker
の中だけなので、せいぜい8つ程度しかViewが存在せず、これでレイアウト処理が重くなるってことはないと思います。
あと、追加したUILabel
のtext
の更新もlayoutSubviews
で行うのは、
date
プロパティのdidSet
では変更を検知できない- 手軽にoverrideして変更を検知できるメソッドがない
addTarget
は使いたくない- カレンダーを閉じるときに
layoutSubviews
が呼ばれる
から。適当に使ってみた限りlayoutSubviews
の呼び出し回数はそんなに多くなかったからこれでいいんじゃない?
ただ、UIDatePickerStyle.compact
でも常にインライン編集になるMacCatalyst(Apple SiliconでiOS版を動かす際も)だとこの方法は使えないので注意してくださいね。
コメント