0

I'm trying to disable default AM/PM in UIDatePicker for 12-Hour format and also try to add colon (:) between Hour and Minute. But failed to do it.

I have set following code in date picker to set time in UIDatePicker.

self.datePicker.datePickerMode = .time

Above code display time in 12-Hour format with AM / PM.

See following image:

UITimePicker

Could anyone help me to fix this issue?

Sagar Chauhan
  • 5,715
  • 2
  • 22
  • 56

3 Answers3

1

I think your best solution is to simply use a normal UIPickerView. This way you can control pretty much everything. As demonstration I created this all-in-code example which should nearly do what you seem to want:

class ViewController: UIViewController {

    private let pickerView: UIPickerView = UIPickerView(frame: CGRect(x: 0.0, y: 300.0, width: 100.0, height: 300.0))

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(pickerView)
        pickerView.dataSource = self
        pickerView.delegate = self
        pickerView.reloadAllComponents()
        pickerView.selectRow(50000, inComponent: 0, animated: false)
        pickerView.selectRow(50000, inComponent: 1, animated: false)

        view.addSubview({
            let label = UILabel(frame: .zero)
            label.text = ":"
            label.sizeToFit()
            label.center = pickerView.center
            label.isUserInteractionEnabled = false
            return label
        }())
    }

}

extension ViewController: UIPickerViewDataSource, UIPickerViewDelegate {

    private func hourValueAtIndex(index: Int) -> Int {
        return index%12
    }
    private func minuteValueAtIndex(index: Int) -> Int {
        return index%60
    }

    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 2
    }
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return 100000
    }

    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        switch component {
        case 0: return String(hourValueAtIndex(index: row))
        case 1: return String(minuteValueAtIndex(index: row))
        default: return ""
        }
    }

    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        let calendarToUse = Calendar.autoupdatingCurrent
        var components = calendarToUse.dateComponents([.year, .month, .day], from: Date())
        components.hour = hourValueAtIndex(index: pickerView.selectedRow(inComponent: 0))
        components.minute = minuteValueAtIndex(index: pickerView.selectedRow(inComponent: 1))
        let selectedDate = calendarToUse.date(from: components)

        print("Selected new time (in UTC): \(selectedDate!)")

        let formatter = DateFormatter()
        formatter.calendar = calendarToUse
        formatter.dateStyle = .short
        formatter.timeStyle = .short
        print("Selected new time (local): \(formatter.string(from: selectedDate!))")
    }

}

I would put this all in storyboard or even in xib to have it reusable if needed. The positioning and styling of colon may need some work but this depends on what designs you want. I just used a 100k value for components to mimic infinite scroll. At least some constant should have been defined for that.

Notice hourValueAtIndex and minuteValueAtIndex; those can be manipulated to use pretty much anything you need. To have it 0-23 hours you would simply use index%24. To have it 1-24 would need a bit more work though: You would return index%24 + 1 but when constructing a date you would need to handle the value of 24 as exception. You would use a 0 and add an extra day. But you can not add an extra day directly to components because you can then overflow for situations like 32nd of July. You do need to use add a day at the end:

selectedDate = calendarToUse.date(byAdding: DateComponents(day: 1), to: selectedDate)
Matic Oblak
  • 16,318
  • 3
  • 24
  • 43
  • Thank you very much. It's really very good solution for me. I have modified some code from above and it's works pretty much better as per my expectation. – Sagar Chauhan Sep 03 '19 at 06:38
0

I believe UIDatePicker uses the local time configuration, so I wouldn't recommend you to change it. Still, changing dateTimePicker.locale should be enough.

Renzo Tissoni
  • 642
  • 9
  • 21
  • I know about this. But, I don't want to set locale. Also, In some countries date picker will use 24 hour format. So it will display without AM / PM. I don't want to use 24 hour format. – Sagar Chauhan Sep 02 '19 at 13:39
  • Then I agree with the comments, using a UIPickerView would be the best solution. – Renzo Tissoni Sep 02 '19 at 13:52
  • @SagarChauhan This is not about country or language, this is *always* about what the user sets in device settings. If you want to override it (why? the user sets that for a reason), you need to use the `en_US_POSIX` locale. – Sulthan Sep 02 '19 at 16:21
  • Yes, I can understand. But mostly user will always set region where he lives, So date picker automatically set as per device's region. – Sagar Chauhan Sep 02 '19 at 16:31
0

UIDatePicker is meant to be used as-is, without customizing it. You're not supposed to mess with it's views, so you might be out of luck. If you want behavior that a UIDatePicker doesn't provide then you might need to create your own control using a UIPickerView.

To quote the docs:

Appearance

The appearance of UIDatePicker is not customizable.

To do that, you'd create a picker view with 2 components, hour and minute, and set up a data source where one of the components has a colon appended to it. Note that the components will scroll the colons as well, which probably isn't exactly what you want.

You could always create a completely custom control, but getting it to do exactly what you want would be a lot of work, and would probably involve using CALayers and CAAnimation, both of which are fairly advanced

Duncan C
  • 128,072
  • 22
  • 173
  • 272