Search code examples
iosswiftuidatepicker

Add Colon (:) between Hour and Minute & Disable default AM/PM in UIDatePicker


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?


Solution

  • 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)