Search code examples
iosuser-interfaceuinavigationbaruidatepickersize-classes

DatePicker goes under UINavigationBar on iPhone, works well on iPad


I have the following code for the DatePickerController:

import UIKit

@objc protocol DatePickerControllerDelegate: AnyObject {
    func datePicker(controller: DatePickerController, didSelect date: Date)
    func datepickerDidDismiss(controller: DatePickerController)
}

final class DatePickerController: UIViewController {
    @objc weak var delegate: DatePickerControllerDelegate?

    @objc public var date: Date {
        get {
            return datePicker.date
        }
        set(value) {
            datePicker.setDate(value, animated: false)
        }
    }

    @objc public lazy var datePicker: UIDatePicker = {
        let v = UIDatePicker()
        v.datePickerMode = .date
        return v
    }()

    override var preferredContentSize: CGSize {
        get {
            return datePicker.frame.size
        }

        set(newValue) {
            super.preferredContentSize = newValue
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(datePicker)
        datePicker.autoresizingMask = [.flexibleTopMargin, .flexibleRightMargin, .flexibleLeftMargin, .flexibleBottomMargin]
        view.backgroundColor = .white
        navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done,
                                                            target: self,
                                                            action: #selector(DatePickerController.doneButtonDidTap))

        navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel,
                                                           target: self,
                                                           action: #selector(DatePickerController.cancelButtonDidTap))
    }

    @objc private func doneButtonDidTap() {
        delegate?.datePicker(controller: self, didSelect: date)
    }

    @objc private func cancelButtonDidTap() {
        dismiss(animated: true, completion: nil)
        delegate?.datepickerDidDismiss(controller: self)
    }
}

It works well on iPad:

ipad view

However, on iPhone the picker slides up under the UINavigationBar:

iphone view

The code used to show the DatePickerController is absolutely the same, the style is UIModalPresentationStylePopover in both cases.

Update: code to present DatePickerController:

  @objc func presentDatePicker() {
    let picker = DatePickerController()
    let navC = UINavigationController(rootViewController: picker)
    present(navC, animated: true, completion: nil)
  }

Solution

  • Fixed using AutoLayout:

    private func configureLayout() {
        datePicker.translatesAutoresizingMaskIntoConstraints = false
        [datePicker.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
         datePicker.leadingAnchor.constraint(equalTo: view.leadingAnchor),
         datePicker.trailingAnchor.constraint(equalTo: view.trailingAnchor),]
            .forEach{$0.isActive = true}
    }