Honestly, I have never really done this so I'm very much lost here. I have an app, presenting a UIPickerView
with days: hours: minutes to the user.
I have to make sure that the minimum selected time is 10 minutes and maximum selected time is 7 days. If the user selects more than 7 days, the days and hours components should be reset back to 0. I also have to get the data and display it in a UILabel
that's above the picker. Here's what I got in terms of code:
struct PollTime {
let time: Int
let description: String
}
struct PollModel {
let days: [PollTime]
let hours: [PollTime]
let minutes: [PollTime]
var currentTime: (day: PollTime, hour: PollTime, minute: PollTime)?
init() {
days = [Int](0...7).map { PollTime(time: $0, description: $0 == 0 || $0 > 1 ? "days" : "day") }
hours = [Int](0...23).map { PollTime(time: $0, description: $0 == 0 || $0 > 1 ? "hours" : "hour") }
minutes = [Int](0...59).map { PollTime(time: $0, description: $0 == 0 || $0 > 1 ? "min" : "min") }
}
func validateCurrenTime() -> Bool {
guard let currentTime = currentTime else { return false }
//if (//TODO: check to see if the currentTime falls within the specified time required.) {
// return true
// }
return false
}
}
var pollModel = PollModel()
extension PostPollVC : UIPickerViewDelegate,UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 3
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
switch component {
case 0:
return pollModel.days.count
case 1:
return pollModel.hours.count
case 2:
return pollModel.minutes.count
default:
return 0
}
}
func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
return pickerView.frame.size.width / 3
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let currentDate = Date()
var dateComponents = DateComponents()
let days = pickerView.selectedRow(inComponent: 0)
let hours = pickerView.selectedRow(inComponent: 1)
let minutes = pickerView.selectedRow(inComponent: 2)
pollModel.currentTime = (PollTime(time: days, description: ""), PollTime(time: hours, description: ""), PollTime(time: minutes, description: ""))
if days != 0 && hours != 0 && minutes != 0 && !pollModel.validateCurrenTime() {
let alert = UIAlertController(title: "Invalid Time", message: "You must select a time greater than 10 minutes, up to 7 days.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default) { [weak self] action in
//TODO: Reset the components of the picker to one less than or great than the invalid choices....
})
self.present(alert, animated: true, completion: nil)
}
dateComponents.day = days
dateComponents.hour = hours
dateComponents.minute = minutes
let calendar = Calendar(identifier: .gregorian)
let futureDate = calendar.date(byAdding: dateComponents, to: currentDate)
print("The current date is: \(currentDate.description)")
print("Your future date is: \(futureDate?.description ?? "")")
}
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
let label = UILabel()
label.textColor = Colors.mainBlueColor
label.textAlignment = .center
label.font = UIFont(name: Fonts.OpenSans_Bold, size: 14)
switch component {
case 0:
label.text = "\(pollModel.days[row].time) \(pollModel.days[row].description)"
return label
case 1:
label.text = "\(pollModel.hours[row].time) \(pollModel.hours[row].description)"
return label
case 2:
label.text = "\(pollModel.minutes[row].time) \(pollModel.minutes[row].description)"
return label
default:
return label
}
}
}
Here is the working code. First important thing: Never forget to set the delegate
and dataSource
of your pickerView
. I understood the absolute maximum was 7 days and not a minute more, but you can tweak the validation conditions as you'd like:
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
@IBOutlet weak var pickerView: UIPickerView!
var pollModel = PollModel()
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 3
}
override func viewDidLoad() {
super.viewDidLoad()
pickerView.delegate = self
pickerView.dataSource = self
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
switch component {
case 0:
return pollModel.days.count
case 1:
return pollModel.hours.count
case 2:
return pollModel.minutes.count
default:
return 0
}
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let currentDate = Date()
var dateComponents = DateComponents()
let days = pickerView.selectedRow(inComponent: 0)
let hours = pickerView.selectedRow(inComponent: 1)
let minutes = pickerView.selectedRow(inComponent: 2)
pollModel.currentTime = (PollTime(time: days, description: ""), PollTime(time: hours, description: ""), PollTime(time: minutes, description: ""))
if !pollModel.validateCurrenTime() {
let alert = UIAlertController(title: "Invalid Time", message: "You must select a time greater than 10 minutes, up to 7 days.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default) { [weak self] action in
[0,1,2].forEach { self?.pickerView.selectRow(0, inComponent: $0, animated: true) }
pickerView.reloadAllComponents()
})
self.present(alert, animated: true, completion: nil)
}
dateComponents.day = days
dateComponents.hour = hours
dateComponents.minute = minutes
let calendar = Calendar(identifier: .gregorian)
let futureDate = calendar.date(byAdding: dateComponents, to: currentDate)
print("The current date is: \(currentDate.description)")
print("Your future date is: \(futureDate?.description ?? "")")
}
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
let label = UILabel()
label.textAlignment = .center
switch component {
case 0:
label.text = "\(pollModel.days[row].time) \(pollModel.days[row].description)"
return label
case 1:
label.text = "\(pollModel.hours[row].time) \(pollModel.hours[row].description)"
return label
case 2:
label.text = "\(pollModel.minutes[row].time) \(pollModel.minutes[row].description)"
return label
default:
return label
}
}
}
struct PollTime {
let time: Int
let description: String
}
struct PollModel {
let days: [PollTime]
let hours: [PollTime]
let minutes: [PollTime]
var currentTime: (day: PollTime, hour: PollTime, minute: PollTime)?
init() {
days = [Int](0...7).map { PollTime(time: $0, description: $0 == 0 || $0 > 1 ? "days" : "day") }
hours = [Int](0...23).map { PollTime(time: $0, description: $0 == 0 || $0 > 1 ? "hours" : "hour") }
minutes = [Int](0...59).map { PollTime(time: $0, description: $0 == 0 || $0 > 1 ? "min" : "min") }
}
private func timeBiggerThan10Minutes() -> Bool {
guard let currentTime = currentTime else {
return false
}
return currentTime.minute.time > 9 || currentTime.hour.time > 0 || currentTime.day.time > 0
}
private func timeLessThan7Days() -> Bool {
guard let currentTime = currentTime else {
return false
}
if currentTime.day.time == 7 {
return currentTime.hour.time == 0 && currentTime.minute.time == 0
}
return currentTime.day.time < 8
}
func validateCurrenTime() -> Bool {
return timeBiggerThan10Minutes() && timeLessThan7Days()
}
}