iosswiftuikituipickerviewuidatepicker

How to prevent interaction with .compact UIDatePicker until a password is entered?


I am using a UIDatePicker in my app to allow picking time:

let startTimePicker = UIDatePicker()
startTimePicker.addTarget.preferredDatePickerStyle = .compact
startTimePicker.addTarget.datePickerMode = .time

However, I only want user to be able to pick this time after they have entered a password correctly. For incorrect password, I want the tap on the picker to be cancelled.

I am able to detect when tap occurs by using editingDidBegin:

startTimePicker.addTarget(self, action: #selector(beginChangingTimes(sender:)), for: .editingDidBegin)

However, I am unable to cancel the tap or at least prevent it from showing the picker until correct password has been entered.

In my beginChangingTimes(sender:) function, I have tried things like setting the picker isEnabled to false then true, setting isUserInteractionEnabled to false then true, calling resignFirstResponder() on the picker, and calling endEditing(true) on the picker. None of these seem to cancel the tap and it just keeps showing the actual picker UI:

enter image description here

How can I cancel it?


Solution

  • You cannot cancel a tap after it begins with .compact style UIDatePicker, but you can:

    1. Prevent the tap using an overlay (UIButton) that intercepts touch.

    2. Or, use a UITextField + .inputView for better control via delegate.

    3. Avoid relying on .editingDidBegin or toggling isEnabled dynamically. It’s too late.

    Recommended Solution: Use a Transparent Overlay or Custom Wrapper

    Instead of trying to cancel the event after it starts, you can block the interaction altogether unless the password is verified.

    Option 1: Wrap the UIDatePicker in a Custom Container with a Transparent Button Overlay

    let containerView = UIView()
    containerView.translatesAutoresizingMaskIntoConstraints = false
    
    let datePicker = UIDatePicker()
    datePicker.preferredDatePickerStyle = .compact
    datePicker.datePickerMode = .time
    datePicker.translatesAutoresizingMaskIntoConstraints = false
    containerView.addSubview(datePicker)
    
    // Overlay button to block interaction
    let blockerButton = UIButton()
    blockerButton.translatesAutoresizingMaskIntoConstraints = false
    blockerButton.backgroundColor = .clear
    blockerButton.addTarget(self, action: #selector(promptForPassword), for: .touchUpInside)
    containerView.addSubview(blockerButton)
    
    // Constraints
    NSLayoutConstraint.activate([
        datePicker.topAnchor.constraint(equalTo: containerView.topAnchor),
        datePicker.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
        datePicker.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
        datePicker.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
    
        blockerButton.topAnchor.constraint(equalTo: containerView.topAnchor),
        blockerButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
        blockerButton.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
        blockerButton.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
    ])
    

    Then:

    @objc func promptForPassword() {
        // Ask for password here
        // If valid, remove blocker
        if isPasswordCorrect {
            blockerButton.removeFromSuperview()
        } else {
            showPasswordError()
        }
    }
    

    Option 2: Use a UITextField + UIDatePicker as inputView (Full Control)

    let timeField = UITextField()
    timeField.placeholder = "Select Time"
    timeField.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(timeField)
    
    let picker = UIDatePicker()
    picker.datePickerMode = .time
    picker.preferredDatePickerStyle = .wheels
    picker.addTarget(self, action: #selector(dateChanged(_:)), for: .valueChanged)
    timeField.inputView = picker
    
    // Password check before editing
    timeField.delegate = self
    

    And conform to:

    extension YourViewController: UITextFieldDelegate {
        func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
            if !isPasswordCorrect {
                showPasswordError()
                return false
            }
            return true
        }
    }