Search code examples
iosswiftiphonexcodeuipickerview

How to prevent the selection of invalid dates in UIPicker view in swift?


I am using the UIPicker view for showing the Day, date and time as per the below screenshot: Desired Output

I have implemented this by using the following code:

import UIKit

class ViewController: UIViewController, UITextFieldDelegate,UIPickerViewDelegate,UIPickerViewDataSource {

   @IBOutlet weak var txtDatePicker: UITextField!
      @IBOutlet var dateLabel:UILabel!
      let datePicker = UIPickerView()
    var pickerDaysYear = [["1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31"],["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],["1AM", "2AM", "3AM", "4AM", "5AM", "6AM", "7AM", "8AM", "9AM", "10AM", "11AM", "12AM","1PM", "2PM", "3PM", "4PM", "5PM", "6PM", "7PM", "8PM", "9PM", "10PM", "11PM", "12PM",]];


  override func viewDidLoad() {
          super.viewDidLoad()
          self.txtDatePicker.delegate = self

          self.datePicker.dataSource = self
          self.datePicker.delegate = self

          self.txtDatePicker.inputView = self.datePicker

          let toolBar = UIToolbar()
          toolBar.barStyle = UIBarStyle.default
          toolBar.isTranslucent = true
          toolBar.tintColor = UIColor(red: 76/255, green: 217/255, blue: 100/255, alpha: 1)
          toolBar.sizeToFit()
    let doneButton = UIBarButtonItem(title: "Done", style: UIBarButtonItem.Style.plain, target: self, action: #selector(self.doneButtonTapped))
          toolBar.setItems([doneButton], animated: false)
          self.txtDatePicker.inputAccessoryView = toolBar
  }
    @objc func doneButtonTapped(){
          self.txtDatePicker.resignFirstResponder()
      }
      override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
          view.endEditing(true)
      }
      override func didReceiveMemoryWarning() {
          super.didReceiveMemoryWarning()
          // Dispose of any resources that can be recreated.
      }


//----------------   PickerView  delegate Method   --------------------------------//

func numberOfComponents(in pickerView: UIPickerView) -> Int {
    return 3
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
    return self.pickerDaysYear[component].count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
    return self.pickerDaysYear[component][row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {

    let days =  pickerDaysYear[0][pickerView.selectedRow(inComponent: 0)]
    let month = pickerDaysYear[1][pickerView.selectedRow(inComponent: 1)]
    let time = pickerDaysYear[2][pickerView.selectedRow(inComponent: 2)]
    txtDatePicker.text =   days + " " + month + " " + time

}
}

I have achieved the desired output but the problem is i can still select invalid dates like "30 February" "31 April" etc ... So is there any way to prevent such invalid dates by being selected??. Right now the output is like :

invalid date selection


Solution

  • In the Gregorian calendar, there is a leap year every four years. There are other rules to determine whether a given year is a leap year. Based on this web site, I have created the following class (MyDate.swift).

    import Foundation
    
    class MyDate: NSObject {
        var year: Int
        var month: Int
        var day: Int
        
        init(year: Int, month: Int, day: Int) {
            self.year = year
            self.month = month
            self.day = day
        }
        
        func validateDate() -> Bool {
            if month == 4 || month == 6 || month == 9 || month == 11 {
                return (day == 30) ? true : false
            }
            else if month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12 {
                return (day == 31) ? true : false
            }
            else {
                if isLeapYear(year: year) {
                    return (day < 30) ? true : false
                } else {
                    return (day < 29) ? true : false
                }
            }
        }
        
        func isLeapYear(year: Int) -> Bool {
            let mod4 = year % 4
            if mod4 == 0 {
                let mod100 = year % 100
                if mod100 == 0 {
                    let mod400 = year % 400
                    if mod400 == 0 {
                        return true
                    } else {
                        return false
                    }
                } else {
                    return true
                }
            } else {
                return false
            }
        }
    }
    

    How to use MyDate

    import UIKit
    
    class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            let myDate = MyDate(year: 2020, month: 3, day: 32)
            let bool = myDate.validateDate()
            print(bool) // false
        }
    }
    

    Instantiate MyDate with a given year, month and the day of the month. Then return a bool object with its validateDate method. So when the user selects a date, instantiate MyDate in the didSelectRow method of UIPicker. If validateDate returns false, set the day of the month to 1 or something.

    txtDatePicker.selectRow(0, inComponent: 0, animated: false)