しめ鯖日記

swift, iPhoneアプリ開発, ruby on rails等のTipsや入門記事書いてます

カレンダー形式のUIDatePickerの正しいサイズを計算する

UIDatePickerですがiOSから下のようなカレンダー形式が使えるようになりました。

f:id:llcc:20220115194554p:plain

コードは下のとおりです。

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let datePicker = UIDatePicker()
        datePicker.preferredDatePickerStyle = .inline
        datePicker.center = view.center
        view.addSubview(datePicker)
    }
}

こちらですが下のようにwidthを広げてしまうと曜日や時間の部分が潰れるという問題があります。
今回はこれを解消するためにsystemLayoutSizeFittingというメソッドを使って正しい高さをセットしてみようと思います。

datePicker.frame.size.width = view.frame.width

f:id:llcc:20220115194908p:plain

systemLayoutSizeFittingとはUIViewのメソッドで、引数に合うSizeを返してくれます。
下のように引数に自身のViewのWidthを入れ、横の優先順位を上げる(defaultHighをセットする)ことで横幅いっぱいの時用のサイズを取得できます。

let targetSize = CGSize(width: view.frame.width, height: 0)
let size = datePicker.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .defaultHigh, verticalFittingPriority: .defaultLow) → (390.0, 397.3333333333333)

あとはdatePickerに取得したsizeをセットすれば曜日や時間部分が潰れなくなります。

datePicker.frame.size = size

f:id:llcc:20220115195555p:plain

datePickerModeを変更した場合、それにあった数字を計算してくれます。

print(datePicker.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .defaultHigh, verticalFittingPriority: .defaultLow)) // → (390.0, 397.3333333333333)
datePicker.datePickerMode = .date
print(datePicker.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .defaultHigh, verticalFittingPriority: .defaultLow)) // → (390.0, 365.0)

ちなみにカレンダー形式UIDatePickerの正しい高さはiOS15とiOS14で微妙に違います。
下がiOS14で高さを計測した時の数字です。

このようにOSのバージョンによって微妙にずれがあるのでできるだけsystemLayoutSizeFittingを使って計算するのが良いかと思います。

print(datePicker.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .defaultHigh, verticalFittingPriority: .defaultLow)) // → (390.0, 405.6666666666667)
datePicker.datePickerMode = .date
print(datePicker.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .defaultHigh, verticalFittingPriority: .defaultLow)) // → (390.0, 368.3333333333333)

ただsystemLayoutSizeFittingは既にDatePickerのサイズが違う状態では違う値になるので注意が必要です。
下のようにviewDidAppearでsystemLayoutSizeFittingを取得し直したらtargetSizeより大きいWidthになってしまいました。
もしviewDidAppear時点でDatePickerのWidthが初期値(320)だと正しい数字が取れるようです。

class ViewController: UIViewController {
    let datePicker = UIDatePicker()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        datePicker.preferredDatePickerStyle = .inline
        let targetSize = CGSize(width: view.frame.width, height: 0)
        datePicker.datePickerMode = .date
        let size = datePicker.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .defaultHigh, verticalFittingPriority: .defaultLow)
        datePicker.frame.size = size
        datePicker.center = view.center
        view.addSubview(datePicker)
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        let targetSize = CGSize(width: view.frame.width, height: 0)
        print(datePicker.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .defaultHigh, verticalFittingPriority: .defaultLow)) // → (407.0, 377.0)
    }
}