Last active
August 29, 2015 14:22
-
-
Save andykog/f9ad1a57757e856a8afc to your computer and use it in GitHub Desktop.
iOS Date Picker with year, month, day and hours segments
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// DeadlinePicker.swift | |
// essayshark | |
// | |
// Created by Andrey Kogut on 6/1/15. | |
// Copyright (c) 2015 ONE FREELANCE LTD. All rights reserved. | |
// | |
import UIKit | |
import ReactiveCocoa | |
class DeadlinePicker: NSObject, UIPickerViewDataSource, UIPickerViewDelegate { | |
private enum Type { | |
case Years, Months, Days, Hours | |
} | |
unowned let datePicker: UIPickerView | |
unowned let targetDate: MutableProperty<NSDate> | |
private let minDate: NSDate | |
private let minDateComponents:NSDateComponents | |
private let selectedDateComponents:NSDateComponents | |
private var selectedDate: NSDate { | |
return NSCalendar.currentCalendar().dateFromComponents(self.selectedDateComponents) | |
?? self.minDate // just in case | |
} | |
init(picker: UIPickerView, minimumHours: Int, targetDate: MutableProperty<NSDate>) { | |
self.datePicker = picker | |
self.targetDate = targetDate | |
self.minDate = NSDate(timeIntervalSinceNow: NSTimeInterval(60 * 60 * minimumHours)) | |
self.minDateComponents = NSCalendar.currentCalendar().components(.CalendarUnitYear | .CalendarUnitMonth | .CalendarUnitDay | .CalendarUnitHour, fromDate: minDate) | |
self.selectedDateComponents = self.minDateComponents | |
super.init() | |
self.datePicker.delegate = self; | |
self.datePicker.dataSource = self; | |
self.pickDate(self.targetDate.value) | |
} | |
private func pickDate(date: NSDate) { | |
let targetDateComponents = NSCalendar.currentCalendar().components(.CalendarUnitYear | .CalendarUnitMonth | .CalendarUnitDay | .CalendarUnitHour, fromDate: date) | |
if let yearIndex = self.indexForValue(ofType: .Years, closestNotSmallerThen: targetDateComponents.year) { | |
self.datePicker.selectRow(yearIndex, inComponent: 0, animated: false) | |
self.pickerView(self.datePicker, didSelectRow: yearIndex, inComponent: 0) | |
} | |
if let monthIndex = self.indexForValue(ofType: .Months, closestNotSmallerThen: targetDateComponents.month) { | |
self.datePicker.selectRow(monthIndex, inComponent: 1, animated: false) | |
self.pickerView(self.datePicker, didSelectRow: monthIndex, inComponent: 1) | |
} | |
if let dayIndex = self.indexForValue(ofType: .Days, closestNotSmallerThen: targetDateComponents.day) { | |
self.datePicker.selectRow(dayIndex, inComponent: 2, animated: false) | |
self.pickerView(self.datePicker, didSelectRow: dayIndex, inComponent: 2) | |
} | |
if let hourIndex = self.indexForValue(ofType: .Hours, closestNotSmallerThen: targetDateComponents.hour) { | |
self.datePicker.selectRow(hourIndex, inComponent: 3, animated: false) | |
self.pickerView(self.datePicker, didSelectRow: hourIndex, inComponent: 3) | |
} | |
} | |
private func indexForValue(ofType type: Type, closestNotSmallerThen desiredValue: Int) -> Int? { | |
var result:(index: Int, value: Int)? | |
for (index, value) in enumerate(self.values(forType: type)) { | |
if value >= desiredValue { | |
if result == nil || value <= result!.value { | |
result = (index: index, value: value) | |
} | |
} | |
} | |
return result?.index | |
} | |
private func values (forType type: Type) -> [Int] { | |
var result = [NSDate]() | |
switch type { | |
case .Years: | |
var dateComponents = NSDateComponents() | |
return (0...10).map { | |
dateComponents.setValue($0, forComponent: NSCalendarUnit.CalendarUnitYear); | |
return NSCalendar.currentCalendar().components(NSCalendarUnit.CalendarUnitYear, fromDate: NSCalendar.currentCalendar().dateByAddingComponents(dateComponents, toDate: self.minDate, options: nil) ?? self.minDate).year | |
} | |
case .Months: | |
if let months = NSCalendar.currentCalendar().rangeOfUnit(.CalendarUnitMonth, inUnit: .CalendarUnitYear, forDate: self.selectedDate).toRange()?.map({ $0 }) { | |
let selectedDateYearsDiff = NSCalendar.currentCalendar().components(.CalendarUnitYear, fromDate: NSDate(), toDate: self.selectedDate, options: nil).year | |
if selectedDateYearsDiff < 1 { | |
return months.filter { $0 >= NSCalendar.currentCalendar().components(.CalendarUnitMonth, fromDate: self.minDate).month } | |
} else { | |
return months | |
} | |
} else { | |
return [] | |
} | |
case .Days: | |
if let days = NSCalendar.currentCalendar().rangeOfUnit(.CalendarUnitDay, inUnit: .CalendarUnitMonth, forDate: self.selectedDate).toRange()?.map({ $0 }) { | |
let selectedDateMonthsDiff = NSCalendar.currentCalendar().components(.CalendarUnitMonth, fromDate: NSDate(), toDate: self.selectedDate, options: nil).month | |
if selectedDateMonthsDiff < 1 { | |
return days.filter { $0 >= NSCalendar.currentCalendar().components(.CalendarUnitDay, fromDate: self.minDate).day } | |
} else { | |
return days | |
} | |
} else { | |
return [] | |
} | |
case .Hours: | |
let selectedDateDaysDiff = NSCalendar.currentCalendar().components(NSCalendarUnit.CalendarUnitDay, fromDate: NSDate(), toDate: self.selectedDate, options: nil).day | |
if selectedDateDaysDiff < 1 { | |
let currentHour = NSCalendar.currentCalendar().components(NSCalendarUnit.CalendarUnitHour, fromDate: minDate).hour | |
return (currentHour...23).map { $0 } | |
} else { | |
return (0...23).map { $0 } | |
} | |
} | |
} | |
private let dFormatter: NSDateFormatter = { | |
let dFormatter = NSDateFormatter() | |
dFormatter.dateFormat = "MMM dd, YYY" | |
return dFormatter | |
}() | |
// MARK: - UIPicker data source & delegate | |
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int { | |
return 4 | |
} | |
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { | |
switch component { | |
case 0: | |
return self.values(forType: .Years).count | |
case 1: | |
return self.values(forType: .Months).count | |
case 2: | |
return self.values(forType: .Days).count | |
case 3: | |
return self.values(forType: .Hours).count | |
default: | |
return 0 | |
} | |
} | |
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! { | |
switch component { | |
case 0: | |
return String(self.values(forType: .Years)[row]) | |
case 1: | |
return (NSCalendar.currentCalendar().monthSymbols[self.values(forType: .Months)[row] - 1] as! NSString).substringWithRange(NSRange(location: 0, length: 3)) | |
case 2: | |
let days = self.values(forType: .Days)[row] | |
return String(days) + { | |
switch days { | |
case 1: return "st" | |
case 2: return "nd" | |
case 3: return "rd" | |
default: return "th" | |
} | |
}() | |
case 3: | |
let hours = self.values(forType: .Hours)[row] | |
return String(hours) + (hours == 0 ? "0:01" : ":00") | |
default: | |
return nil | |
} | |
} | |
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { | |
switch component { | |
case 0: | |
self.selectedDateComponents.setValue(self.values(forType: .Years)[row], forComponent: NSCalendarUnit.CalendarUnitYear) | |
self.datePicker.reloadComponent(1) | |
self.datePicker.reloadComponent(2) | |
self.datePicker.reloadComponent(3) | |
case 1: | |
self.selectedDateComponents.setValue(self.values(forType: .Months)[row], forComponent: NSCalendarUnit.CalendarUnitMonth) | |
self.datePicker.reloadComponent(2) | |
self.datePicker.reloadComponent(3) | |
case 2: | |
self.selectedDateComponents.setValue(self.values(forType: .Days)[row], forComponent: NSCalendarUnit.CalendarUnitDay) | |
self.datePicker.reloadComponent(3) | |
case 3: | |
self.selectedDateComponents.setValue(self.values(forType: .Hours)[row], forComponent: NSCalendarUnit.CalendarUnitHour) | |
default: | |
break | |
} | |
self.targetDate.put(selectedDate) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment