From 001b6d3de242b5daeae4183f7e91f9dd18bfc300 Mon Sep 17 00:00:00 2001 From: Daryll Herberger Date: Wed, 1 Apr 2015 15:24:53 -0700 Subject: [PATCH 1/2] Adding range picker support. Includes CGFloat and time (NSDate) range picker examples, including an update to the sample project to demonstrate usage. --- BSModalPickerView/BSModalDatePickerView.m | 2 + .../BSModalFloatRangePickerView.h | 19 + .../BSModalFloatRangePickerView.m | 92 ++++ BSModalPickerView/BSModalPickerView.m | 1 + BSModalPickerView/BSModalRangePickerBase.h | 21 + BSModalPickerView/BSModalRangePickerBase.m | 129 +++++ .../BSModalTimeRangePickerView.h | 18 + .../BSModalTimeRangePickerView.m | 91 ++++ .../ModalPickerDemo.xcodeproj/project.pbxproj | 18 + .../ModalPickerDemo/ViewController.h | 4 +- .../ModalPickerDemo/ViewController.m | 61 ++- .../en.lproj/ViewController.xib | 487 ++++++++++-------- 12 files changed, 722 insertions(+), 221 deletions(-) create mode 100644 BSModalPickerView/BSModalFloatRangePickerView.h create mode 100644 BSModalPickerView/BSModalFloatRangePickerView.m create mode 100644 BSModalPickerView/BSModalRangePickerBase.h create mode 100644 BSModalPickerView/BSModalRangePickerBase.m create mode 100644 BSModalPickerView/BSModalTimeRangePickerView.h create mode 100644 BSModalPickerView/BSModalTimeRangePickerView.m diff --git a/BSModalPickerView/BSModalDatePickerView.m b/BSModalPickerView/BSModalDatePickerView.m index 5c4bcf5..c537f2e 100644 --- a/BSModalPickerView/BSModalDatePickerView.m +++ b/BSModalPickerView/BSModalDatePickerView.m @@ -29,6 +29,8 @@ - (id)initWithDate:(NSDate *)date { - (UIView *)pickerWithFrame:(CGRect)pickerFrame { UIDatePicker *datePicker = [[UIDatePicker alloc] initWithFrame:pickerFrame]; + datePicker.backgroundColor = [UIColor whiteColor]; + if (self.selectedDate) { datePicker.date = self.selectedDate; } diff --git a/BSModalPickerView/BSModalFloatRangePickerView.h b/BSModalPickerView/BSModalFloatRangePickerView.h new file mode 100644 index 0000000..9a1c9a5 --- /dev/null +++ b/BSModalPickerView/BSModalFloatRangePickerView.h @@ -0,0 +1,19 @@ +// +// BSModalFloatRangePickerView.h +// GolfNow +// +// Created by Cameron Hall on 3/26/15. +// Copyright (c) 2015 Vertigo. All rights reserved. +// +// + +#import "BSModalRangePickerBase.h" + +@interface BSModalFloatRangePickerView : BSModalRangePickerBase + +- (instancetype)initWithFloatInterval:(CGFloat)floatInterval andMinRange:(CGFloat)minRangeValue andMaxRange:(CGFloat)maxRangeValue; + +- (void)setSelectedMinValue:(CGFloat)minValue; +- (void)setSelectedMaxValue:(CGFloat)maxValue; + +@end \ No newline at end of file diff --git a/BSModalPickerView/BSModalFloatRangePickerView.m b/BSModalPickerView/BSModalFloatRangePickerView.m new file mode 100644 index 0000000..bc228d9 --- /dev/null +++ b/BSModalPickerView/BSModalFloatRangePickerView.m @@ -0,0 +1,92 @@ +// +// BSModalFloatRangePickerView.m +// GolfNow +// +// Created by Cameron Hall on 3/26/15. +// Copyright (c) 2015 Vertigo. All rights reserved. +// + +#import "BSModalFloatRangePickerView.h" + +@interface BSModalFloatRangePickerView() + +@property (nonatomic) CGFloat floatInterval; + +@property (nonatomic) CGFloat minRangeValue; +@property (nonatomic) CGFloat maxRangeValue; + +@end + +@implementation BSModalFloatRangePickerView + +- (instancetype)initWithFloatInterval:(CGFloat)floatInterval andMinRange:(CGFloat)minRangeValue andMaxRange:(CGFloat)maxRangeValue { + if (self = [super init]) { + self.minRangeValue = minRangeValue; + self.maxRangeValue = maxRangeValue; + self.floatInterval = floatInterval; + + if (self.minRangeValue > self.maxRangeValue) { + self.maxRangeValue = self.minRangeValue; + } + + [self calculateRangeValues]; + } + + return self; +} + +#pragma mark - BSModalRangePickerBase + +- (void)calculateRangeValues { + CGFloat rangeDiff = self.maxRangeValue - self.minRangeValue; + NSInteger numberOfValues = rangeDiff / self.floatInterval + 1; + NSMutableArray *rangeValues = [NSMutableArray array]; + + for (NSInteger i = 0; i < numberOfValues; ++i) { + NSNumber *value = [NSNumber numberWithFloat:i * self.floatInterval]; + [rangeValues addObject:value]; + } + + self.rangeValues = rangeValues; +} + +- (NSString *)formatValue:(id)value forComponent:(NSInteger)component { + NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; + numberFormatter.numberStyle = NSNumberFormatterDecimalStyle; + + NSString *formattedValue = [numberFormatter stringFromNumber:value]; + return formattedValue; +} + +#pragma mark - Public + +- (void)setSelectedMinValue:(CGFloat)minValue { + for (NSNumber *value in self.rangeValues) { + if ([BSModalFloatRangePickerView floatValue:[value floatValue] isEqualToFloat:minValue]) { + [self setSelectedMinRowIndex:[self.rangeValues indexOfObject:value]]; + break; + } + } +} + +- (void)setSelectedMaxValue:(CGFloat)maxValue { + for (NSNumber *value in self.rangeValues) { + if ([BSModalFloatRangePickerView floatValue:[value floatValue] isEqualToFloat:maxValue]) { + [self setSelectedMaxRowIndex:[self.rangeValues indexOfObject:value]]; + break; + } + } +} + +#pragma mark - Private + ++ (BOOL)floatValue:(CGFloat)firstValue isEqualToFloat:(CGFloat)secondValue { + double diff = fabs(firstValue - secondValue); + firstValue = fabs(firstValue); + secondValue = fabs(secondValue); + double largest = (secondValue > firstValue) ? secondValue : firstValue; + return (diff <= largest * FLT_EPSILON); +} + +@end + diff --git a/BSModalPickerView/BSModalPickerView.m b/BSModalPickerView/BSModalPickerView.m index 716d46c..a5a3020 100644 --- a/BSModalPickerView/BSModalPickerView.m +++ b/BSModalPickerView/BSModalPickerView.m @@ -31,6 +31,7 @@ - (id)initWithValues:(NSArray *)values { - (UIView *)pickerWithFrame:(CGRect)pickerFrame { UIPickerView *pickerView = [[UIPickerView alloc] initWithFrame:pickerFrame]; + pickerView.backgroundColor = [UIColor whiteColor]; pickerView.dataSource = self; pickerView.delegate = self; pickerView.showsSelectionIndicator = YES; diff --git a/BSModalPickerView/BSModalRangePickerBase.h b/BSModalPickerView/BSModalRangePickerBase.h new file mode 100644 index 0000000..0b3a936 --- /dev/null +++ b/BSModalPickerView/BSModalRangePickerBase.h @@ -0,0 +1,21 @@ +// +// BSModalRangePickerBase.h +// GolfNow +// +// Created by Cameron Hall on 3/26/15. +// Copyright (c) 2015 Vertigo. All rights reserved. +// + +#import "BSModalPickerBase.h" + +@interface BSModalRangePickerBase : BSModalPickerBase + +@property (nonatomic, strong) NSArray *rangeValues; + +@property (nonatomic) NSUInteger selectedMinRowIndex; +@property (nonatomic) NSUInteger selectedMaxRowIndex; + +- (id)selectedMaxValue; +- (id)selectedMinValue; + +@end diff --git a/BSModalPickerView/BSModalRangePickerBase.m b/BSModalPickerView/BSModalRangePickerBase.m new file mode 100644 index 0000000..0b01dcb --- /dev/null +++ b/BSModalPickerView/BSModalRangePickerBase.m @@ -0,0 +1,129 @@ +// +// BSModalRangePickerBase.m +// GolfNow +// +// Created by Cameron Hall on 3/26/15. +// Copyright (c) 2015 Vertigo. All rights reserved. +// + +#import "BSModalRangePickerBase.h" + +@implementation BSModalRangePickerBase + +- (UIView *)pickerWithFrame:(CGRect)pickerFrame { + UIPickerView *pickerView = [[UIPickerView alloc] initWithFrame:pickerFrame]; + pickerView.showsSelectionIndicator = YES; + pickerView.backgroundColor = [UIColor whiteColor]; + pickerView.delegate = self; + pickerView.dataSource = self; + self.picker = pickerView; + + return pickerView; +} + +#pragma mark - Private + +- (id)valueAtIndex:(NSInteger)index forComponent:(NSInteger)component { + + if (component == 1) { + index += self.selectedMinRowIndex; + } + + return [self.rangeValues objectAtIndex:index]; +} + +- (NSInteger)indexOfValue:(id)value forComponent:(NSInteger)component { + NSInteger result = [self.rangeValues indexOfObject:value]; + + if (component == 1) { + result -= self.selectedMinRowIndex; + } + + return result; +} + +- (NSString *)formattedValueAtIndex:(NSInteger)index forComponent:(NSInteger)component { + id value = [self valueAtIndex:index forComponent:component]; + return [self formatValue:value forComponent:component]; +} + +#pragma mark - Public + +- (id)selectedMaxValue { + NSInteger index = self.selectedMaxRowIndex + self.selectedMinRowIndex; + + if (self.rangeValues && [self.rangeValues count] > index) { + return [self.rangeValues objectAtIndex:index]; + } + + return nil; +} + +- (id)selectedMinValue { + if (self.rangeValues && [self.rangeValues count] > self.selectedMinRowIndex) { + return [self.rangeValues objectAtIndex:self.selectedMinRowIndex]; + } + + return nil; +} + +#pragma mark - Subclass-implemented methods + +- (void)calculateRangeValues { + [NSException raise:NSGenericException + format:@"calculateRangeValues: must be implemented by a subclass"]; +} + +- (NSString *)formatValue:(id)value forComponent:(NSInteger)component { + [NSException raise:NSGenericException + format:@"formatValue:forComponent: must be implemented by a subclass"]; + return nil; +} + +#pragma mark - Picker View Data Source + +- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView { + return 2; +} + +- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component { + NSInteger rowCount = [self.rangeValues count]; + + if (component == 1) { + rowCount -= self.selectedMinRowIndex; + } + + return rowCount; +} + +#pragma mark - Picker View Delegate + +- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component { + NSString *result = [self formattedValueAtIndex:row forComponent:component]; + + return result; +} + +- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component { + if (component == 0) { + id maxValue = [self valueAtIndex:self.selectedMaxRowIndex forComponent:1]; + + self.selectedMinRowIndex = row; + [pickerView reloadAllComponents]; + + NSInteger newMaxValueIndex = [self indexOfValue:maxValue forComponent:1]; + + if (newMaxValueIndex < 0) { + newMaxValueIndex = 0; + } + + self.selectedMaxRowIndex = newMaxValueIndex; + + [pickerView selectRow:newMaxValueIndex inComponent:1 animated:NO]; + + } else { + self.selectedMaxRowIndex = row; + } +} + +@end diff --git a/BSModalPickerView/BSModalTimeRangePickerView.h b/BSModalPickerView/BSModalTimeRangePickerView.h new file mode 100644 index 0000000..2229aa0 --- /dev/null +++ b/BSModalPickerView/BSModalTimeRangePickerView.h @@ -0,0 +1,18 @@ +// +// BSModalTimeRangePickerView.h +// GolfNow +// +// Created by Daryll Herberger on 3/24/15. +// Copyright (c) 2015 Vertigo. All rights reserved. +// + +#import "BSModalRangePickerBase.h" + +@interface BSModalTimeRangePickerView : BSModalRangePickerBase + +- (instancetype)initWithTimeInterval:(double)timeInterval andMinRange:(NSDate*)minRangeValue andMaxRange:(NSDate *)maxRangeValue; + +- (void)setSelectedMinValue:(NSDate *)minValue; +- (void)setSelectedMaxValue:(NSDate *)maxValue; + +@end diff --git a/BSModalPickerView/BSModalTimeRangePickerView.m b/BSModalPickerView/BSModalTimeRangePickerView.m new file mode 100644 index 0000000..53c763d --- /dev/null +++ b/BSModalPickerView/BSModalTimeRangePickerView.m @@ -0,0 +1,91 @@ +// +// BSModalTimeRangePickerView.m +// GolfNow +// +// Created by Daryll Herberger on 3/24/15. +// Copyright (c) 2015 Vertigo. All rights reserved. +// + +#import "BSModalTimeRangePickerView.h" + +@interface BSModalTimeRangePickerView () + +@property (nonatomic) double numberOfMinutesInRange; +@property (nonatomic) NSUInteger rangeIntervalInMinutes; + +@property (nonatomic, strong) NSDate *maxRangeValue; +@property (nonatomic, strong) NSDate *minRangeValue; + +@end + +@implementation BSModalTimeRangePickerView + +- (instancetype)initWithTimeInterval:(double)timeInterval andMinRange:(NSDate *)minRangeValue andMaxRange:(NSDate *)maxRangeValue { + if (self = [super init]) { + self.rangeIntervalInMinutes = timeInterval; + self.minRangeValue = minRangeValue; + self.maxRangeValue = maxRangeValue; + + if ([minRangeValue earlierDate:maxRangeValue] == maxRangeValue) { + self.minRangeValue = maxRangeValue; + } + + [self calculateRangeValues]; + } + + return self; +} + +#pragma mark - BSModalRangePickerBase + +- (void)calculateRangeValues { + self.numberOfMinutesInRange = [self.maxRangeValue timeIntervalSinceDate:self.minRangeValue] / 60; + + NSMutableArray *values = [NSMutableArray array]; + NSInteger numberOfRowsInComponent = self.numberOfMinutesInRange / self.rangeIntervalInMinutes; + + for (NSInteger i = 0; i < numberOfRowsInComponent; i++) { + NSInteger numberOfMinutesFromRangeMin = self.rangeIntervalInMinutes * i; + NSDate *componentRowValue = [self.minRangeValue dateByAddingTimeInterval:numberOfMinutesFromRangeMin * 60]; + [values addObject:componentRowValue]; + } + + self.rangeValues = values; +} + +- (NSString *)formatValue:(id)value forComponent:(NSInteger)component { + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setTimeStyle:NSDateFormatterShortStyle]; + [dateFormatter setDateStyle:NSDateFormatterNoStyle]; + [dateFormatter setLocale:[NSLocale currentLocale]]; + + NSString *result = [dateFormatter stringFromDate:value]; + + return result; +} + +#pragma mark - Public + +- (void)setSelectedMinValue:(NSDate *)minValue { + if (minValue) { + for (NSDate *value in self.rangeValues) { + if ([value timeIntervalSinceDate:minValue] == 0) { + [self setSelectedMinRowIndex:[self.rangeValues indexOfObject:value]]; + break; + } + } + } +} + +- (void)setSelectedMaxValue:(NSDate *)maxValue { + if (maxValue) { + for (NSDate *value in self.rangeValues) { + if ([value timeIntervalSinceDate:maxValue] == 0) { + [self setSelectedMaxRowIndex:[self.rangeValues indexOfObject:value]]; + break; + } + } + } +} + +@end diff --git a/ModalPickerDemo/ModalPickerDemo.xcodeproj/project.pbxproj b/ModalPickerDemo/ModalPickerDemo.xcodeproj/project.pbxproj index eb99450..25a6ca6 100644 --- a/ModalPickerDemo/ModalPickerDemo.xcodeproj/project.pbxproj +++ b/ModalPickerDemo/ModalPickerDemo.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 312762041ACCA0B3001084EF /* BSModalFloatRangePickerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 312761FF1ACCA0B3001084EF /* BSModalFloatRangePickerView.m */; }; + 312762051ACCA0B3001084EF /* BSModalRangePickerBase.m in Sources */ = {isa = PBXBuildFile; fileRef = 312762011ACCA0B3001084EF /* BSModalRangePickerBase.m */; }; + 312762061ACCA0B3001084EF /* BSModalTimeRangePickerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 312762031ACCA0B3001084EF /* BSModalTimeRangePickerView.m */; }; B500DAFB17563C4900E08D3B /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B500DAFA17563C4900E08D3B /* UIKit.framework */; }; B500DAFD17563C4900E08D3B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B500DAFC17563C4900E08D3B /* Foundation.framework */; }; B500DAFF17563C4900E08D3B /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B500DAFE17563C4900E08D3B /* CoreGraphics.framework */; }; @@ -24,6 +27,12 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 312761FE1ACCA0B3001084EF /* BSModalFloatRangePickerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BSModalFloatRangePickerView.h; sourceTree = ""; }; + 312761FF1ACCA0B3001084EF /* BSModalFloatRangePickerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BSModalFloatRangePickerView.m; sourceTree = ""; }; + 312762001ACCA0B3001084EF /* BSModalRangePickerBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BSModalRangePickerBase.h; sourceTree = ""; }; + 312762011ACCA0B3001084EF /* BSModalRangePickerBase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BSModalRangePickerBase.m; sourceTree = ""; }; + 312762021ACCA0B3001084EF /* BSModalTimeRangePickerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BSModalTimeRangePickerView.h; sourceTree = ""; }; + 312762031ACCA0B3001084EF /* BSModalTimeRangePickerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BSModalTimeRangePickerView.m; sourceTree = ""; }; B500DAF717563C4900E08D3B /* ModalPickerDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ModalPickerDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; B500DAFA17563C4900E08D3B /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; B500DAFC17563C4900E08D3B /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; @@ -122,10 +131,16 @@ children = ( B5A7E07917565C8F005A49C4 /* BSModalDatePickerView.h */, B5A7E07A17565C8F005A49C4 /* BSModalDatePickerView.m */, + 312761FE1ACCA0B3001084EF /* BSModalFloatRangePickerView.h */, + 312761FF1ACCA0B3001084EF /* BSModalFloatRangePickerView.m */, B5A7E07B17565C8F005A49C4 /* BSModalPickerBase.h */, B5A7E07C17565C8F005A49C4 /* BSModalPickerBase.m */, B5A7E07D17565C8F005A49C4 /* BSModalPickerView.h */, B5A7E07E17565C8F005A49C4 /* BSModalPickerView.m */, + 312762001ACCA0B3001084EF /* BSModalRangePickerBase.h */, + 312762011ACCA0B3001084EF /* BSModalRangePickerBase.m */, + 312762021ACCA0B3001084EF /* BSModalTimeRangePickerView.h */, + 312762031ACCA0B3001084EF /* BSModalTimeRangePickerView.m */, ); name = BSModalPickerView; path = ../BSModalPickerView; @@ -197,9 +212,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 312762041ACCA0B3001084EF /* BSModalFloatRangePickerView.m in Sources */, B500DB0717563C4900E08D3B /* main.m in Sources */, B500DB0B17563C4900E08D3B /* AppDelegate.m in Sources */, + 312762051ACCA0B3001084EF /* BSModalRangePickerBase.m in Sources */, B500DB1417563C4900E08D3B /* ViewController.m in Sources */, + 312762061ACCA0B3001084EF /* BSModalTimeRangePickerView.m in Sources */, B5A7E07F17565C8F005A49C4 /* BSModalDatePickerView.m in Sources */, B5A7E08017565C8F005A49C4 /* BSModalPickerBase.m in Sources */, B5A7E08117565C8F005A49C4 /* BSModalPickerView.m in Sources */, diff --git a/ModalPickerDemo/ModalPickerDemo/ViewController.h b/ModalPickerDemo/ModalPickerDemo/ViewController.h index 9a93cfd..f0e1ba0 100644 --- a/ModalPickerDemo/ModalPickerDemo/ViewController.h +++ b/ModalPickerDemo/ModalPickerDemo/ViewController.h @@ -10,9 +10,11 @@ @interface ViewController : UIViewController -@property (weak, nonatomic) IBOutlet UILabel *dateLabel; +@property (weak, nonatomic) IBOutlet UILabel *resultLabel; - (IBAction)onSelectColor:(id)sender; - (IBAction)onSelectDate:(id)sender; +- (IBAction)onSelectFloatRange:(id)sender; +- (IBAction)onSelectTimeRange:(id)sender; @end diff --git a/ModalPickerDemo/ModalPickerDemo/ViewController.m b/ModalPickerDemo/ModalPickerDemo/ViewController.m index b7d71d1..fd61497 100644 --- a/ModalPickerDemo/ModalPickerDemo/ViewController.m +++ b/ModalPickerDemo/ModalPickerDemo/ViewController.m @@ -9,6 +9,8 @@ #import "ViewController.h" #import "BSModalPickerView.h" #import "BSModalDatePickerView.h" +#import "BSModalFloatRangePickerView.h" +#import "BSModalTimeRangePickerView.h" @interface ViewController () { NSInteger _lastSelectedIndex; @@ -57,9 +59,66 @@ - (IBAction)onSelectDate:(id)sender { if (madeChoice) { NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; [dateFormatter setDateStyle:NSDateFormatterLongStyle]; - self.dateLabel.text = [dateFormatter stringFromDate:datePicker.selectedDate]; + [self.resultLabel setText:[dateFormatter stringFromDate:datePicker.selectedDate]]; } }]; } +- (IBAction)onSelectFloatRange:(id)sender { + BSModalFloatRangePickerView *floatRangePicker = [[BSModalFloatRangePickerView alloc] initWithFloatInterval:0.525 andMinRange:0.0 andMaxRange:10.5]; + [floatRangePicker presentInView:self.view + withBlock:^(BOOL madeChoice) { + if (madeChoice) { + NSNumber *selectedMin = [floatRangePicker selectedMinValue]; + NSNumber *selectedMax = [floatRangePicker selectedMaxValue]; + + NSString *rangeString = [NSString stringWithFormat:@"%@ - %@", [selectedMin stringValue], [selectedMax stringValue]]; + + [self.resultLabel setText:rangeString]; + } + }]; +} + +- (IBAction)onSelectTimeRange:(id)sender { + // Get a minimum value starting at the last half hour + NSDateComponents *time = [[NSCalendar currentCalendar] + components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit + fromDate:[NSDate date]]; + NSInteger minutes = [time minute]; + float minuteUnit = floor((float) minutes / 30.0); + minutes = minuteUnit * 30.0; + [time setMinute: minutes]; + NSDate *minRangeDate = [[NSCalendar currentCalendar] dateFromComponents:time]; + + // Get a maximum value ending 6 hours away or end of day, end of the hour + minutes = 0; + NSInteger hours = [time hour]; + + if (hours + 6 < 24) { + [time setHour:hours + 6]; + } else { + [time setHour:23]; + } + + NSDate *maxRangeDate = [[NSCalendar currentCalendar] dateFromComponents:time]; + + BSModalTimeRangePickerView *timeRangePicker = [[BSModalTimeRangePickerView alloc] initWithTimeInterval:15 andMinRange:minRangeDate andMaxRange:maxRangeDate]; + [timeRangePicker presentInView:self.view + withBlock:^(BOOL madeChoice) { + if (madeChoice) { + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setDateStyle:NSDateFormatterNoStyle]; + [dateFormatter setTimeStyle:NSDateFormatterShortStyle]; + [dateFormatter setLocale:[NSLocale currentLocale]]; + + NSString *selectedMin = [dateFormatter stringFromDate:[timeRangePicker selectedMinValue]]; + NSString *selectedMax = [dateFormatter stringFromDate:[timeRangePicker selectedMaxValue]]; + + NSString *rangeString = [NSString stringWithFormat:@"%@ - %@", selectedMin, selectedMax]; + + [self.resultLabel setText:rangeString]; + } + }]; +} + @end diff --git a/ModalPickerDemo/ModalPickerDemo/en.lproj/ViewController.xib b/ModalPickerDemo/ModalPickerDemo/en.lproj/ViewController.xib index 6356121..96a681d 100644 --- a/ModalPickerDemo/ModalPickerDemo/en.lproj/ViewController.xib +++ b/ModalPickerDemo/ModalPickerDemo/en.lproj/ViewController.xib @@ -2,16 +2,15 @@ 1552 - 12D78 - 3084 - 1187.37 - 626.00 + 14C1514 + 6751 + 1344.72 + 757.30 com.apple.InterfaceBuilder.IBCocoaTouchPlugin - 2083 + 6736 - IBNSLayoutConstraint IBProxyObject IBUIButton IBUILabel @@ -40,8 +39,9 @@ 292 - {{195, 86}, {105, 44}} + {{194, 86}, {106, 44}} + _NS:9 NO @@ -49,25 +49,26 @@ 0 0 1 - Select Date + + 1 + MC4xOTYwNzg0MzE0IDAuMzA5ODAzOTIxNiAwLjUyMTU2ODYyNzUAA + 3 MQA - - 1 - MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA - + Select Date 3 MC41AA 2 + 30 15 - - Helvetica-Bold + + HelveticaNeue 15 16 @@ -75,8 +76,9 @@ 292 - {{20, 86}, {112, 44}} + {{20, 86}, {111, 44}} + _NS:9 NO @@ -84,28 +86,27 @@ 0 0 1 - Select Color + - - 1 - MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA - + Select Color - + 292 {{20, 170}, {280, 34}} + + _NS:9 NO YES 7 NO IBCocoaTouchFramework - (select a date) + (select a date or range) 1 MC4yNzM2MDE1MTc0IDAuNDI5NTgxMzUxOSAwLjMzNzc4NDA2NDgAA @@ -115,18 +116,66 @@ 1 2 + 30 21 - Helvetica-Bold + HelveticaNeue 21 16 NO + YES + + + + 292 + {{20, 243}, {148, 44}} + + + + NO + IBCocoaTouchFramework + 0 + 0 + 1 + + + Select float range + + + 2 + 0 + 15 + + + HelveticaNeue-Bold + 15 + 16 + + + + + 292 + {{21, 333}, {147, 44}} + + + NO + IBCocoaTouchFramework + 0 + 0 + 1 + + + Select time range + + + {{0, 20}, {320, 548}} + 1 @@ -136,6 +185,8 @@ IBUIScreenMetrics + IBCocoaTouchFramework + iPhone 4-inch YES @@ -147,15 +198,13 @@ {568, 320} - IBCocoaTouchFramework - Retina 4 Full Screen 2 IBCocoaTouchFramework - + view @@ -166,11 +215,11 @@ - dateLabel + resultLabel - 37 + 51 @@ -190,6 +239,24 @@ 35 + + + onSelectFloatRange: + + + 7 + + 52 + + + + onSelectTimeRange: + + + 7 + + 53 + @@ -214,121 +281,11 @@ 6 - - - 6 - 0 - - 6 - 1 - - 20 - - 1000 - - 8 - 29 - 3 - - - - 5 - 0 - - 5 - 1 - - 20 - - 1000 - - 8 - 29 - 3 - - - - 6 - 0 - - 6 - 1 - - 20 - - 1000 - - 8 - 29 - 3 - - - - 3 - 0 - - 3 - 1 - - 170 - - 1000 - - 3 - 9 - 3 - - - - 5 - 0 - - 5 - 1 - - 20 - - 1000 - - 8 - 29 - 3 - - - - 3 - 0 - - 3 - 1 - - 86 - - 1000 - - 3 - 9 - 3 - - - - 11 - 0 - - 11 - 1 - - 0.0 - - 1000 - - 6 - 24 - 2 - + + @@ -342,67 +299,21 @@ - - 17 - - - - - 18 - - - - - 19 - - - - - 21 - - - 22 - - - - 8 - 0 - - 0 - 1 - - 34 - - 1000 - - 3 - 9 - 1 - - - - - - 31 - - - - - 29 - + - 28 - + 38 + + - 23 - + 50 + @@ -413,45 +324,183 @@ UIResponder com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin - - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin - - - - - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin - - - - - - - - - + com.apple.InterfaceBuilder.IBCocoaTouchPlugin - - 37 + 53 + + + + + ViewController + UIViewController + + id + id + id + id + + + + onSelectColor: + id + + + onSelectDate: + id + + + onSelectFloatRange: + id + + + onSelectTimeRange: + id + + + + resultLabel + UILabel + + + resultLabel + + resultLabel + UILabel + + + + IBProjectSource + ../ModalPickerDemo/ViewController.h + + + + ViewController + + id + id + id + id + + + + onSelectColor: + id + + + onSelectDate: + id + + + onSelectFloatRange: + id + + + onSelectTimeRange: + id + + + + IBProjectSource + ../ModalPickerDemo/ViewController.m + + + + + + UIButton + UIControl + + IBFrameworkSource + UIKit.framework/Headers/UIButton.h + + + + UIControl + UIView + + IBFrameworkSource + UIKit.framework/Headers/UIControl.h + + + + UIGestureRecognizer + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIGestureRecognizer.h + + + + UILabel + UIView + + IBFrameworkSource + UIKit.framework/Headers/UILabel.h + + + + UIResponder + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIResponder.h + + + + UISearchBar + UIView + + IBFrameworkSource + UIKit.framework/Headers/UISearchBar.h + + + + UISearchDisplayController + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UISearchDisplayController.h + + + + UIView + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIView.h + + + + UIViewController + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIViewController.h + + + - 0 IBCocoaTouchFramework + NO + + com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 + + YES 3 - YES - 2083 From 69065280848eccaa6d607a1ac432013d3adf8220 Mon Sep 17 00:00:00 2001 From: Daryll Herberger Date: Thu, 2 Apr 2015 17:25:30 -0700 Subject: [PATCH 2/2] Fixing range picker -- now works correctly with setting up new instances using previously selected values, updated sample project --- .../BSModalFloatRangePickerView.h | 3 -- .../BSModalFloatRangePickerView.m | 38 ++++++++------- BSModalPickerView/BSModalRangePickerBase.h | 4 +- BSModalPickerView/BSModalRangePickerBase.m | 43 ++++++++++++++++- .../BSModalTimeRangePickerView.h | 3 -- .../BSModalTimeRangePickerView.m | 46 +++++++++++-------- .../ModalPickerDemo/ViewController.m | 27 +++++++++-- 7 files changed, 112 insertions(+), 52 deletions(-) diff --git a/BSModalPickerView/BSModalFloatRangePickerView.h b/BSModalPickerView/BSModalFloatRangePickerView.h index 9a1c9a5..22b4dcc 100644 --- a/BSModalPickerView/BSModalFloatRangePickerView.h +++ b/BSModalPickerView/BSModalFloatRangePickerView.h @@ -13,7 +13,4 @@ - (instancetype)initWithFloatInterval:(CGFloat)floatInterval andMinRange:(CGFloat)minRangeValue andMaxRange:(CGFloat)maxRangeValue; -- (void)setSelectedMinValue:(CGFloat)minValue; -- (void)setSelectedMaxValue:(CGFloat)maxValue; - @end \ No newline at end of file diff --git a/BSModalPickerView/BSModalFloatRangePickerView.m b/BSModalPickerView/BSModalFloatRangePickerView.m index bc228d9..d30cde6 100644 --- a/BSModalPickerView/BSModalFloatRangePickerView.m +++ b/BSModalPickerView/BSModalFloatRangePickerView.m @@ -53,33 +53,31 @@ - (void)calculateRangeValues { - (NSString *)formatValue:(id)value forComponent:(NSInteger)component { NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; numberFormatter.numberStyle = NSNumberFormatterDecimalStyle; - + NSString *formattedValue = [numberFormatter stringFromNumber:value]; return formattedValue; } -#pragma mark - Public - -- (void)setSelectedMinValue:(CGFloat)minValue { - for (NSNumber *value in self.rangeValues) { - if ([BSModalFloatRangePickerView floatValue:[value floatValue] isEqualToFloat:minValue]) { - [self setSelectedMinRowIndex:[self.rangeValues indexOfObject:value]]; - break; - } - } -} - -- (void)setSelectedMaxValue:(CGFloat)maxValue { - for (NSNumber *value in self.rangeValues) { - if ([BSModalFloatRangePickerView floatValue:[value floatValue] isEqualToFloat:maxValue]) { - [self setSelectedMaxRowIndex:[self.rangeValues indexOfObject:value]]; - break; - } +- (NSUInteger)indexForSelectedValue:(id)value { + __block NSUInteger result = 0; + + if (value) { + CGFloat floatValue = [value floatValue]; + + [self.rangeValues enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + NSNumber *rangeValue = (NSNumber *)obj; + CGFloat rangeFloatValue = [rangeValue floatValue]; + + if ([BSModalFloatRangePickerView floatValue:floatValue isEqualToFloat:rangeFloatValue]) { + result = idx; + *stop = YES; + } + }]; } + + return result; } -#pragma mark - Private - + (BOOL)floatValue:(CGFloat)firstValue isEqualToFloat:(CGFloat)secondValue { double diff = fabs(firstValue - secondValue); firstValue = fabs(firstValue); diff --git a/BSModalPickerView/BSModalRangePickerBase.h b/BSModalPickerView/BSModalRangePickerBase.h index 0b3a936..c714ff9 100644 --- a/BSModalPickerView/BSModalRangePickerBase.h +++ b/BSModalPickerView/BSModalRangePickerBase.h @@ -15,7 +15,7 @@ @property (nonatomic) NSUInteger selectedMinRowIndex; @property (nonatomic) NSUInteger selectedMaxRowIndex; -- (id)selectedMaxValue; -- (id)selectedMinValue; +@property (nonatomic) id selectedMaxValue; +@property (nonatomic) id selectedMinValue; @end diff --git a/BSModalPickerView/BSModalRangePickerBase.m b/BSModalPickerView/BSModalRangePickerBase.m index 0b01dcb..63a615d 100644 --- a/BSModalPickerView/BSModalRangePickerBase.m +++ b/BSModalPickerView/BSModalRangePickerBase.m @@ -67,6 +67,38 @@ - (id)selectedMinValue { return nil; } +- (void)setSelectedMinValue:(id)selectedMinValue { + NSUInteger index = [self indexForSelectedValue:selectedMinValue]; + + if ([self.rangeValues count] > index) { + [self setSelectedMinRowIndex:index]; + + UIPickerView *pickerView = (UIPickerView *)self.picker; + + if (pickerView) { + [pickerView selectRow:index inComponent:0 animated:NO]; + } + } +} + +- (void)setSelectedMaxValue:(id)selectedMaxValue { + NSUInteger index = [self indexForSelectedValue:selectedMaxValue]; + + if (self.selectedMinRowIndex <= index) { + index -= self.selectedMinRowIndex; + } + + if ([self.rangeValues count] > index) { + [self setSelectedMaxRowIndex:index]; + + UIPickerView *pickerView = (UIPickerView *)self.picker; + + if (pickerView) { + [pickerView selectRow:index inComponent:1 animated:NO]; + } + } +} + #pragma mark - Subclass-implemented methods - (void)calculateRangeValues { @@ -80,6 +112,12 @@ - (NSString *)formatValue:(id)value forComponent:(NSInteger)component { return nil; } +- (NSUInteger)indexForSelectedValue:(id)value { + [NSException raise:NSGenericException + format:@"indexForSelectedValue: must be implemented by a subclass"]; + return 0; +} + #pragma mark - Picker View Data Source - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView { @@ -117,10 +155,13 @@ - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComp newMaxValueIndex = 0; } + if ([self.rangeValues count] <= newMaxValueIndex) { + newMaxValueIndex = [self.rangeValues count] - 1; + } + self.selectedMaxRowIndex = newMaxValueIndex; [pickerView selectRow:newMaxValueIndex inComponent:1 animated:NO]; - } else { self.selectedMaxRowIndex = row; } diff --git a/BSModalPickerView/BSModalTimeRangePickerView.h b/BSModalPickerView/BSModalTimeRangePickerView.h index 2229aa0..7fa61f4 100644 --- a/BSModalPickerView/BSModalTimeRangePickerView.h +++ b/BSModalPickerView/BSModalTimeRangePickerView.h @@ -12,7 +12,4 @@ - (instancetype)initWithTimeInterval:(double)timeInterval andMinRange:(NSDate*)minRangeValue andMaxRange:(NSDate *)maxRangeValue; -- (void)setSelectedMinValue:(NSDate *)minValue; -- (void)setSelectedMaxValue:(NSDate *)maxValue; - @end diff --git a/BSModalPickerView/BSModalTimeRangePickerView.m b/BSModalPickerView/BSModalTimeRangePickerView.m index 53c763d..c6acbc7 100644 --- a/BSModalPickerView/BSModalTimeRangePickerView.m +++ b/BSModalPickerView/BSModalTimeRangePickerView.m @@ -64,28 +64,38 @@ - (NSString *)formatValue:(id)value forComponent:(NSInteger)component { return result; } -#pragma mark - Public - -- (void)setSelectedMinValue:(NSDate *)minValue { - if (minValue) { - for (NSDate *value in self.rangeValues) { - if ([value timeIntervalSinceDate:minValue] == 0) { - [self setSelectedMinRowIndex:[self.rangeValues indexOfObject:value]]; - break; +- (NSUInteger)indexForSelectedValue:(id)value { + __block NSUInteger result = 0; + + if (value && [value isKindOfClass:[NSDate class]]) { + NSTimeInterval valueTimeOnly = [self timeIntervalOnlyForDate:value]; + + [self.rangeValues enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + NSDate *rangeValue = (NSDate *)obj; + NSTimeInterval rangeValueTimeOnly = [self timeIntervalOnlyForDate:rangeValue]; + + if (rangeValueTimeOnly == valueTimeOnly) { + result = idx; + *stop = YES; } - } + }]; } + + return result; } -- (void)setSelectedMaxValue:(NSDate *)maxValue { - if (maxValue) { - for (NSDate *value in self.rangeValues) { - if ([value timeIntervalSinceDate:maxValue] == 0) { - [self setSelectedMaxRowIndex:[self.rangeValues indexOfObject:value]]; - break; - } - } - } +- (NSTimeInterval)timeIntervalOnlyForDate:(NSDate *)date { + NSDateComponents *dateComponents = [[NSCalendar currentCalendar] + components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit + fromDate:date]; + + [dateComponents setHour:0]; + [dateComponents setMinute:0]; + + NSDate *dateWithNoTime = [[NSCalendar currentCalendar] dateFromComponents:dateComponents]; + NSTimeInterval timeOnly = [date timeIntervalSinceDate:dateWithNoTime]; + + return timeOnly; } @end diff --git a/ModalPickerDemo/ModalPickerDemo/ViewController.m b/ModalPickerDemo/ModalPickerDemo/ViewController.m index fd61497..44ab5d9 100644 --- a/ModalPickerDemo/ModalPickerDemo/ViewController.m +++ b/ModalPickerDemo/ModalPickerDemo/ViewController.m @@ -16,6 +16,12 @@ @interface ViewController () { NSInteger _lastSelectedIndex; } +@property (nonatomic, strong) NSNumber *lastSelectedFloatRangeMin; +@property (nonatomic, strong) NSNumber *lastSelectedFloatRangeMax; + +@property (nonatomic, strong) NSDate *lastSelectedTimeRangeMin; +@property (nonatomic, strong) NSDate *lastSelectedTimeRangeMax; + @end @implementation ViewController @@ -66,13 +72,17 @@ - (IBAction)onSelectDate:(id)sender { - (IBAction)onSelectFloatRange:(id)sender { BSModalFloatRangePickerView *floatRangePicker = [[BSModalFloatRangePickerView alloc] initWithFloatInterval:0.525 andMinRange:0.0 andMaxRange:10.5]; + + [floatRangePicker setSelectedMinValue:self.lastSelectedFloatRangeMin]; + [floatRangePicker setSelectedMaxValue:self.lastSelectedFloatRangeMax]; + [floatRangePicker presentInView:self.view withBlock:^(BOOL madeChoice) { if (madeChoice) { - NSNumber *selectedMin = [floatRangePicker selectedMinValue]; - NSNumber *selectedMax = [floatRangePicker selectedMaxValue]; + self.lastSelectedFloatRangeMin = [floatRangePicker selectedMinValue]; + self.lastSelectedFloatRangeMax = [floatRangePicker selectedMaxValue]; - NSString *rangeString = [NSString stringWithFormat:@"%@ - %@", [selectedMin stringValue], [selectedMax stringValue]]; + NSString *rangeString = [NSString stringWithFormat:@"%@ - %@", [self.lastSelectedFloatRangeMin stringValue], [self.lastSelectedFloatRangeMax stringValue]]; [self.resultLabel setText:rangeString]; } @@ -103,16 +113,23 @@ - (IBAction)onSelectTimeRange:(id)sender { NSDate *maxRangeDate = [[NSCalendar currentCalendar] dateFromComponents:time]; BSModalTimeRangePickerView *timeRangePicker = [[BSModalTimeRangePickerView alloc] initWithTimeInterval:15 andMinRange:minRangeDate andMaxRange:maxRangeDate]; + + [timeRangePicker setSelectedMinValue:self.lastSelectedTimeRangeMin]; + [timeRangePicker setSelectedMaxValue:self.lastSelectedTimeRangeMax]; + [timeRangePicker presentInView:self.view withBlock:^(BOOL madeChoice) { if (madeChoice) { + self.lastSelectedTimeRangeMin = [timeRangePicker selectedMinValue]; + self.lastSelectedTimeRangeMax = [timeRangePicker selectedMaxValue]; + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; [dateFormatter setDateStyle:NSDateFormatterNoStyle]; [dateFormatter setTimeStyle:NSDateFormatterShortStyle]; [dateFormatter setLocale:[NSLocale currentLocale]]; - NSString *selectedMin = [dateFormatter stringFromDate:[timeRangePicker selectedMinValue]]; - NSString *selectedMax = [dateFormatter stringFromDate:[timeRangePicker selectedMaxValue]]; + NSString *selectedMin = [dateFormatter stringFromDate:self.lastSelectedTimeRangeMin]; + NSString *selectedMax = [dateFormatter stringFromDate:self.lastSelectedTimeRangeMax]; NSString *rangeString = [NSString stringWithFormat:@"%@ - %@", selectedMin, selectedMax];