iosswiftswiftuiuidatepicker

How to give UIDatePicker intrinsic height when embedded in UIViewRepresentable


I would like to create a UIViewRepresentable holding a UITextField using an UIDatePicker as inputView

While the code below works fine then the DatePicker is directly used as inputView, it does not work anymore, when the DatePicker is embedded in some containerView (required to add additional controls).

Neither adding a size constraint to the DatePicker or the containerView, nor using a ContainerView with intrinsic size, changed the result.

Any idea what is wrong and how to correctly size the DatePicker?

struct DatePickerTextField: UIViewRepresentable {
    private let dateFormatter: DateFormatter = {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "dd.MM.yyyy"
        return dateFormatter
    }()
    
    public var placeholder: String
    @Binding public var date: Date?
    
    func makeUIView(context: Context) -> UITextField {
        let textField = UITextField()
        let datePicker = UIDatePicker()
        
        let container = DatePickerContainer()
        datePicker.translatesAutoresizingMaskIntoConstraints = false
        container.addSubview(datePicker)
        NSLayoutConstraint.activate([
            datePicker.leadingAnchor.constraint(equalTo: container.leadingAnchor),
            datePicker.trailingAnchor.constraint(equalTo: container.trailingAnchor),
            datePicker.topAnchor.constraint(equalTo: container.topAnchor),
            datePicker.bottomAnchor.constraint(equalTo: container.bottomAnchor),
            //datePicker.heightAnchor.constraint(equalToConstant: 300)
        ])
        
        
        datePicker.datePickerMode = .date
        datePicker.preferredDatePickerStyle = .inline
        
        //textField.inputView = datePicker  // correct height
        textField.inputView = container
        
        // Accessory View
        let toolbar = UIToolbar()
        toolbar.sizeToFit()
        let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
        let doneButton = UIBarButtonItem(title: "Done",
                                         style: .plain,
                                         target: context.coordinator,
                                         action: #selector(context.coordinator.doneButtonAction))
        toolbar.setItems([flexibleSpace, doneButton], animated: true)
        textField.inputAccessoryView = toolbar
        
        context.coordinator.dateChanged = {
            self.date = datePicker.date
        }
        
        context.coordinator.doneButtonTapped = {
            if self.date == nil {
                self.date = datePicker.date
            }
            textField.resignFirstResponder()
        }
        
        return textField
    }
    
    class DatePickerContainer: UIView {
        override var intrinsicContentSize: CGSize {
            return CGSize(width: UIView.noIntrinsicMetric, height: 300)
        }
    }
    
    func updateUIView(_ uiView: UITextField, context: Context) {
        if let selectedDate = self.date {
            uiView.text = self.dateFormatter.string(from: selectedDate)
        }
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator()
    }
    
    class Coordinator {
        public var dateChanged: (() -> Void)?
        public var doneButtonTapped: (() -> Void)?
        
        @objc func dateValueChanged() {
            self.dateChanged?()
        }
        
        @objc func doneButtonAction() {
            self.doneButtonTapped?()
        }
    }
}

Solution

  • All you need to do is to set bounds.size.height to the desired height, and this will become the height of the keyboard. You don't need to override intrinsicContentSize at all.

    let container = DatePickerContainer()
    container.bounds.size.height = 300
    // everything else stays the same
    

    Or if you would like to use AutoLayout, you need to disable translatesAutoresizingMaskIntoConstraints on container as well.

    container.translatesAutoresizingMaskIntoConstraints = false
    
    // now these constraints will resize the container to 300px tall
    NSLayoutConstraint.activate([
        datePicker.leadingAnchor.constraint(equalTo: container.leadingAnchor),
        datePicker.trailingAnchor.constraint(equalTo: container.trailingAnchor),
        datePicker.topAnchor.constraint(equalTo: container.topAnchor),
        datePicker.bottomAnchor.constraint(equalTo: container.bottomAnchor),
        datePicker.heightAnchor.constraint(equalToConstant: 300)
    ])