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版を動かす際も)だとこの方法は使えないので注意してくださいね。
コメント