I need a UIDatePicker
for selecting Month and Year only. I checked the class reference documents. Looks like UIDatePicker
is a UIView
.
I just reset the day to first of the month when the value change event happens as below. So When the user selects a day, it scrolls back to 1st. I use following for selecting Start date (month & year).
-(void) resetDay {
NSDate *currentDate = [startDatePicker date];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *componentsYear = [gregorian components:(NSYearCalendarUnit| NSMonthCalendarUnit) fromDate:currentDate];
NSInteger yearNum = [ componentsYear year];
NSInteger monthNum = [componentsYear month];
NSLog(@"Month %d, Year %d", monthNum, yearNum);
NSDateComponents *componentStartDate = [[NSDateComponents alloc] init];
[componentStartDate setDay:1];
[componentStartDate setMonth:monthNum];
[componentStartDate setYear:yearNum];
NSDate *startDate = [gregorian dateFromComponents:componentStartDate];
[startDatePicker setDate:startDate animated:YES];
[componentStartDate release];
[gregorian release];
}
For selecting the End Date (month & year) it is little bit longer, because I wanted to set the day to 31st or 30th of the month selected.
-(NSInteger) findDaysInYear {
NSDate *currentDate = [NSDate date];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *componentsYear = [gregorian components:(NSYearCalendarUnit) fromDate:currentDate];
NSInteger yearNum = [ componentsYear year];
NSDateComponents *componentStartDate = [[NSDateComponents alloc] init];
[componentStartDate setDay:1];
[componentStartDate setMonth:1];
[componentStartDate setYear:yearNum];
NSDate *startDate = [gregorian dateFromComponents:componentStartDate];
NSLog(@"Start date set to %@", startDate);
NSDateComponents *componentEndDate = [[NSDateComponents alloc] init];
[componentEndDate setDay:31];
[componentEndDate setMonth:12];
[componentEndDate setYear:yearNum];
NSDate *endDate = [gregorian dateFromComponents:componentEndDate];
NSLog(@"End date set to %@", endDate);
NSUInteger unitFlags = NSDayCalendarUnit ;
NSDateComponents *componentsDay = [gregorian components:unitFlags fromDate:startDate toDate:endDate options:0];
NSInteger days = [componentsDay day] +1;
NSLog(@"Number of days in the year:%d, is %d", yearNum,days);
[componentEndDate release];
[componentStartDate release];
[gregorian release];
if (days != 365 && days != 366) {
return 366;
}
return days;
}
-(NSInteger) findDaysInMonth:(NSInteger) monthNum {
NSInteger days = 30;
switch (monthNum) {
case 1:
days = 31;
break;
case 2:
if([self findDaysInYear] == 366) days = 29;
else days = 28;
break;
case 3:
days = 31;
break;
case 4:
days = 30;
break;
case 5:
days = 31;
break;
case 6:
days = 30;
break;
case 7:
days = 31;
break;
case 8:
days = 31;
break;
case 9:
days = 30;
break;
case 10:
days = 31;
break;
case 11:
days = 30;
break;
case 12:
days = 31;
break;
default:
break;
}
return days;
}
-(void) resetDay {
NSDate *currentDate = [endDatePicker date];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *componentsYear = [gregorian components:(NSYearCalendarUnit| NSMonthCalendarUnit) fromDate:currentDate];
NSInteger yearNum = [ componentsYear year];
NSInteger monthNum = [componentsYear month];
NSDateComponents *componentStartDate = [[NSDateComponents alloc] init];
[componentStartDate setDay:[self findDaysInMonth:monthNum]];
[componentStartDate setMonth:monthNum];
[componentStartDate setYear:yearNum];
NSDate *startDate = [gregorian dateFromComponents:componentStartDate];
[endDatePicker setDate:startDate animated:YES];
[componentStartDate release];
[gregorian release];
}
Rewritten answer of RossP in Swift 4:
import Foundation
@objc class ShortDatePickerView: UIPickerView {
enum Component: Int {
case Month = 0
case Year = 1
}
let LABEL_TAG = 43
let bigRowCount = 1000
let numberOfComponentsRequired = 2
let months = ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"]
var years: [String] {
get {
var years: [String] = [String]()
for i in minYear...maxYear {
years.append("\(i)")
}
return years;
}
}
var bigRowMonthsCount: Int {
get {
return bigRowCount * months.count
}
}
var bigRowYearsCount: Int {
get {
return bigRowCount * years.count
}
}
var monthSelectedTextColor: UIColor?
var monthTextColor: UIColor?
var yearSelectedTextColor: UIColor?
var yearTextColor: UIColor?
var monthSelectedFont: UIFont?
var monthFont: UIFont?
var yearSelectedFont: UIFont?
var yearFont: UIFont?
let rowHeight: NSInteger = 44
/**
Will be returned in user's current TimeZone settings
**/
var date: Date {
get {
let month = self.months[selectedRow(inComponent: Component.Month.rawValue) % months.count]
let year = self.years[selectedRow(inComponent: Component.Year.rawValue) % years.count]
let formatter = DateFormatter()
formatter.dateFormat = "MM yyyy"
return formatter.date(from: "\(month) \(year)")!
}
}
var minYear: Int!
var maxYear: Int!
override init(frame: CGRect) {
super.init(frame: frame)
loadDefaultParameters()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
loadDefaultParameters()
}
override func awakeFromNib() {
super.awakeFromNib()
loadDefaultParameters()
}
func loadDefaultParameters() {
minYear = Calendar.current.dateComponents([.year], from: Date()).year
maxYear = minYear! + 10
self.delegate = self
self.dataSource = self
monthSelectedTextColor = .blue
monthTextColor = .black
yearSelectedTextColor = .blue
yearTextColor = .black
monthSelectedFont = .boldSystemFont(ofSize: 17)
monthFont = .boldSystemFont(ofSize: 17)
yearSelectedFont = .boldSystemFont(ofSize: 17)
yearFont = .boldSystemFont(ofSize: 17)
}
func setup(minYear: NSInteger, andMaxYear maxYear: NSInteger) {
self.minYear = minYear
if maxYear > minYear {
self.maxYear = maxYear
} else {
self.maxYear = minYear + 10
}
}
func selectToday() {
selectRow(todayIndexPath.row, inComponent: Component.Month.rawValue, animated: false)
selectRow(todayIndexPath.section, inComponent: Component.Year.rawValue, animated: false)
}
var todayIndexPath: NSIndexPath {
get {
var row = 0.0
var section = 0.0
for cellMonth in months {
if cellMonth == currentMonthName {
row = Double(months.index(of: cellMonth)!)
row = row + Double(bigRowMonthsCount / 2)
break
}
}
for cellYear in years {
if cellYear == currentYearName {
section = Double(years.index(of: cellYear)!)
section = section + Double(bigRowYearsCount / 2)
break
}
}
return NSIndexPath(row: Int(row), section: Int(section))
}
}
var currentMonthName: String {
get {
let formatter = DateFormatter()
let locale = Locale.init(identifier: "en_US")
formatter.locale = locale
formatter.dateFormat = "MM"
return formatter.string(from: Date())
}
}
var currentYearName: String {
get {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy"
return formatter.string(from: Date())
}
}
func selectedColorForComponent(component: NSInteger) -> UIColor {
if component == Component.Month.rawValue {
return monthSelectedTextColor!
}
return yearSelectedTextColor!
}
func colorForComponent(component: NSInteger) -> UIColor {
if component == Component.Month.rawValue {
return monthTextColor!
}
return yearTextColor!
}
func selectedFontForComponent(component: NSInteger) -> UIFont {
if component == Component.Month.rawValue {
return monthSelectedFont!
}
return yearSelectedFont!
}
func fontForComponent(component: NSInteger) -> UIFont {
if component == Component.Month.rawValue {
return monthFont!
}
return yearFont!
}
func titleForRow(row: Int, forComponent component: Int) -> String? {
if component == Component.Month.rawValue {
return self.months[row % self.months.count]
}
return self.years[row % self.years.count]
}
func labelForComponent(component: NSInteger) -> UILabel {
let frame = CGRect(x: 0.0, y: 0.0, width: bounds.size.width, height: CGFloat(rowHeight))
let label = UILabel(frame: frame)
label.textAlignment = .center
label.backgroundColor = .clear
label.isUserInteractionEnabled = false
label.tag = LABEL_TAG
return label
}
}
extension ShortDatePickerView: UIPickerViewDelegate, UIPickerViewDataSource {
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return numberOfComponentsRequired
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if (component == Component.Month.rawValue) {
return bigRowMonthsCount
} else {
return bigRowYearsCount
}
}
func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
return self.bounds.size.width / CGFloat(numberOfComponentsRequired)
}
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
var selected = false
if component == Component.Month.rawValue {
let monthName = self.months[(row % self.months.count)]
if monthName == currentMonthName {
selected = true
}
} else {
let yearName = self.years[(row % self.years.count)]
if yearName == currentYearName {
selected = true
}
}
var returnView: UILabel
if view?.tag == LABEL_TAG {
returnView = view as! UILabel
} else {
returnView = labelForComponent(component: component)
}
returnView.font = selected ? selectedFontForComponent(component: component) : fontForComponent(component: component)
returnView.textColor = selected ? selectedColorForComponent(component: component) : colorForComponent(component: component)
returnView.text = titleForRow(row: row, forComponent: component)
return returnView
}
func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
return CGFloat(rowHeight)
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 2
}
}
Yeah, you probably want to just make your own picker. You don't have to subclass it or anything, though; just use a generic UIPickerView
and return appropriate values from your UIPickerViewDelegate
/UIPickerViewDataSource
methods.
You can very easily hide the day column like this:
datePicker.subviews[0].subviews[0].subviews[1].hidden = true
Enjoy :)
Here is a solution to get the same effect. For using this snippet of code you should replace UIPickerView
to CDatePickerViewEx
in nib file in "Custom class" of "Indentity inspector".
.h file
#import <UIKit/UIKit.h>
@interface CDatePickerViewEx : UIPickerView <UIPickerViewDelegate, UIPickerViewDataSource>
@property (nonatomic, strong, readonly) NSDate *date;
-(void)selectToday;
@end
.m file
#import "CDatePickerViewEx.h"
// Identifiers of components
#define MONTH ( 0 )
#define YEAR ( 1 )
// Identifies for component views
#define LABEL_TAG 43
@interface CDatePickerViewEx()
@property (nonatomic, strong) NSIndexPath *todayIndexPath;
@property (nonatomic, strong) NSArray *months;
@property (nonatomic, strong) NSArray *years;
-(NSArray *)nameOfYears;
-(NSArray *)nameOfMonths;
-(CGFloat)componentWidth;
-(UILabel *)labelForComponent:(NSInteger)component selected:(BOOL)selected;
-(NSString *)titleForRow:(NSInteger)row forComponent:(NSInteger)component;
-(NSIndexPath *)todayPath;
-(NSInteger)bigRowMonthCount;
-(NSInteger)bigRowYearCount;
-(NSString *)currentMonthName;
-(NSString *)currentYearName;
@end
@implementation CDatePickerViewEx
const NSInteger bigRowCount = 1000;
const NSInteger minYear = 2008;
const NSInteger maxYear = 2030;
const CGFloat rowHeight = 44.f;
const NSInteger numberOfComponents = 2;
@synthesize todayIndexPath;
@synthesize months;
@synthesize years = _years;
-(void)awakeFromNib
{
[super awakeFromNib];
self.months = [self nameOfMonths];
self.years = [self nameOfYears];
self.todayIndexPath = [self todayPath];
self.delegate = self;
self.dataSource = self;
[self selectToday];
}
-(NSDate *)date
{
NSInteger monthCount = [self.months count];
NSString *month = [self.months objectAtIndex:([self selectedRowInComponent:MONTH] % monthCount)];
NSInteger yearCount = [self.years count];
NSString *year = [self.years objectAtIndex:([self selectedRowInComponent:YEAR] % yearCount)];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; [formatter setDateFormat:@"MMMM:yyyy"];
NSDate *date = [formatter dateFromString:[NSString stringWithFormat:@"%@:%@", month, year]];
return date;
}
#pragma mark - UIPickerViewDelegate
-(CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component
{
return [self componentWidth];
}
-(UIView *)pickerView: (UIPickerView *)pickerView
viewForRow: (NSInteger)row
forComponent: (NSInteger)component
reusingView: (UIView *)view
{
BOOL selected = NO;
if(component == MONTH)
{
NSInteger monthCount = [self.months count];
NSString *monthName = [self.months objectAtIndex:(row % monthCount)];
NSString *currentMonthName = [self currentMonthName];
if([monthName isEqualToString:currentMonthName] == YES)
{
selected = YES;
}
}
else
{
NSInteger yearCount = [self.years count];
NSString *yearName = [self.years objectAtIndex:(row % yearCount)];
NSString *currenrYearName = [self currentYearName];
if([yearName isEqualToString:currenrYearName] == YES)
{
selected = YES;
}
}
UILabel *returnView = nil;
if(view.tag == LABEL_TAG)
{
returnView = (UILabel *)view;
}
else
{
returnView = [self labelForComponent: component selected: selected];
}
returnView.textColor = selected ? [UIColor blueColor] : [UIColor blackColor];
returnView.text = [self titleForRow:row forComponent:component];
return returnView;
}
-(CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component
{
return rowHeight;
}
#pragma mark - UIPickerViewDataSource
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
return numberOfComponents;
}
-(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
if(component == MONTH)
{
return [self bigRowMonthCount];
}
return [self bigRowYearCount];
}
#pragma mark - Util
-(NSInteger)bigRowMonthCount
{
return [self.months count] * bigRowCount;
}
-(NSInteger)bigRowYearCount
{
return [self.years count] * bigRowCount;
}
-(CGFloat)componentWidth
{
return self.bounds.size.width / numberOfComponents;
}
-(NSString *)titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
if(component == MONTH)
{
NSInteger monthCount = [self.months count];
return [self.months objectAtIndex:(row % monthCount)];
}
NSInteger yearCount = [self.years count];
return [self.years objectAtIndex:(row % yearCount)];
}
-(UILabel *)labelForComponent:(NSInteger)component selected:(BOOL)selected
{
CGRect frame = CGRectMake(0.f, 0.f, [self componentWidth],rowHeight);
UILabel *label = [[UILabel alloc] initWithFrame:frame];
label.textAlignment = UITextAlignmentCenter;
label.backgroundColor = [UIColor clearColor];
label.textColor = selected ? [UIColor blueColor] : [UIColor blackColor];
label.font = [UIFont boldSystemFontOfSize:18.f];
label.userInteractionEnabled = NO;
label.tag = LABEL_TAG;
return label;
}
-(NSArray *)nameOfMonths
{
NSDateFormatter *dateFormatter = [NSDateFormatter new];
return [dateFormatter standaloneMonthSymbols];
}
-(NSArray *)nameOfYears
{
NSMutableArray *years = [NSMutableArray array];
for(NSInteger year = minYear; year <= maxYear; year++)
{
NSString *yearStr = [NSString stringWithFormat:@"%i", year];
[years addObject:yearStr];
}
return years;
}
-(void)selectToday
{
[self selectRow: self.todayIndexPath.row
inComponent: MONTH
animated: NO];
[self selectRow: self.todayIndexPath.section
inComponent: YEAR
animated: NO];
}
-(NSIndexPath *)todayPath // row - month ; section - year
{
CGFloat row = 0.f;
CGFloat section = 0.f;
NSString *month = [self currentMonthName];
NSString *year = [self currentYearName];
//set table on the middle
for(NSString *cellMonth in self.months)
{
if([cellMonth isEqualToString:month])
{
row = [self.months indexOfObject:cellMonth];
row = row + [self bigRowMonthCount] / 2;
break;
}
}
for(NSString *cellYear in self.years)
{
if([cellYear isEqualToString:year])
{
section = [self.years indexOfObject:cellYear];
section = section + [self bigRowYearCount] / 2;
break;
}
}
return [NSIndexPath indexPathForRow:row inSection:section];
}
-(NSString *)currentMonthName
{
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"MMMM"];
return [formatter stringFromDate:[NSDate date]];
}
-(NSString *)currentYearName
{
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy"];
return [formatter stringFromDate:[NSDate date]];
}
@end
No, this cannot be done using the stock UIDatePicker
.
UIDatePickerModeDate
The date picker displays months, days of the month, and years. The exact order of these items depends on the locale setting. An example of this mode is [ November | 15 | 2007 ].
Source: UIDatePicker class reference
I recommend customizing a UIPickerView to use two components and populating its rows with month and year symbols retrieved from NSDateFormatter.