UIDatePicker странное поведение при установке minuteInterval - PullRequest
5 голосов
/ 05 августа 2011

Следующий код отображает странное поведение под iOS 4.3 (возможно, и в другой версии). В этом примере отображается UIDatePicker, чья дата установлена ​​на 4 Aug 2011 2:31 PM. UILabel ниже UIDatePicker отображает дату для справки. Три UIButtons ниже, обозначенные 1, 5, 10, устанавливают minuteInterval на UIDatePicker.

Нажатие 1 - показывает, что выбранная дата в UIDatePicker равна 4 Aug 2011 2:31 PM, а минутный интервал равен 1, что и следовало ожидать.

Нажатие 5 - показывает, что выбранная дата в UIDatePicker равна 4 Aug 2011 2:35 PM, а минутный интервал равен 5, что и следовало ожидать (можно утверждать, что время должно округляться, но это не большая проблема ).

Нажатие 10 - показывает, что выбранная дата в UIDatePicker равна 4 Aug 2011 2:10 PM, а минутный интервал равен 10. Хорошо, минутный интервал правильный, но выбранное время равно 2:10? Можно было бы ожидать 2:40 (если округлено в большую сторону) или 2:30 (если округлено в меньшую сторону).

BugDatePickerVC.h

#import <UIKit/UIKit.h>

@interface BugDatePickerVC : UIViewController {
    NSDateFormatter *dateFormatter;
    NSDate *date;
    UIDatePicker *datePicker;
    UILabel *dateL;
    UIButton *oneB;
    UIButton *fiveB;
    UIButton *tenB;
}

- (void) buttonEventTouchDown:(id)sender;

@end

BugDatePickerVC.m

import "BugDatePickerVC.h"

@implementation BugDatePickerVC

- (id) init
{
    if ( !(self = [super init]) )
    {
        return self;
    }

    dateFormatter = [[NSDateFormatter alloc] init];
    dateFormatter.dateFormat = @"d MMM yyyy h:mm a";

    date = [[dateFormatter dateFromString:@"4 Aug 2011 2:31 PM"] retain];

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    // Date picker
    datePicker = [[UIDatePicker alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 216.0f)];
    datePicker.date = date;
    datePicker.minuteInterval = 1;
    [self.view addSubview:datePicker];

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    // Label with the date.
    dateL = [[UILabel alloc] initWithFrame:CGRectMake(10.0f, 230.0f, 300.0f, 32.0f)];
    dateL.text = [dateFormatter stringFromDate:date];
    [self.view addSubview:dateL];

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    // Button that set the date picker's minute interval to 1.
    oneB = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    oneB.frame = CGRectMake(10.0f, 270.0f, 100.0f, 32.0f);
    oneB.tag = 1;
    [oneB setTitle:@"1" forState:UIControlStateNormal];
    [oneB   addTarget:self
               action:@selector(buttonEventTouchDown:)
     forControlEvents:UIControlEventTouchDown];
    [self.view addSubview:oneB];

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    // Button that set the date picker's minute interval to 5.
    fiveB = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    fiveB.frame = CGRectMake(10.0f, 310.0f, 100.0f, 32.0f);
    fiveB.tag = 5;
    [fiveB setTitle:@"5" forState:UIControlStateNormal];
    [fiveB  addTarget:self
               action:@selector(buttonEventTouchDown:)
     forControlEvents:UIControlEventTouchDown];
    [self.view addSubview:fiveB];

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    // Button that set the date picker's minute interval to 10.
    tenB = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    tenB.frame = CGRectMake(10.0f, 350.0f, 100.0f, 32.0f);
    tenB.tag = 10;
    [tenB setTitle:@"10" forState:UIControlStateNormal];
    [tenB   addTarget:self
               action:@selector(buttonEventTouchDown:)
     forControlEvents:UIControlEventTouchDown];
    [self.view addSubview:tenB];

    return self;
}

- (void) dealloc
{
    [dateFormatter release];
    [date release];
    [datePicker release];
    [dateL release];
    [oneB release];
    [fiveB release];
    [tenB release];

    [super dealloc];
}

- (void) buttonEventTouchDown:(id)sender
{
    datePicker.minuteInterval = [sender tag];
}

Ответы [ 7 ]

8 голосов
/ 05 августа 2011

Хорошо, я смог изменить поведение, явно установив значение даты UIDatePicker равным дате, округленной до минутного интервала, используя следующий код:

- (void) handleUIControlEventTouchDown:(id)sender
{
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Set the date picker's minute interval.
    NSInteger minuteInterval  = [sender tag];

    // Setting the date picker's minute interval can change what is selected on
    // the date picker's UI to a wrong date, it does not effect the date
    // picker's date value.
    //
    // For example the date picker's date value is 2:31 and then minute interval
    // is set to 10.  The date value is still 2:31, but 2:10 is selected on the
    // UI, not 2:40 (rounded up) or 2:30 (rounded down).
    //
    // The code that follow's setting the date picker's minute interval
    // addresses fixing the date value (and the selected date on the UI display)
    // .  In the example above both would be 2:30.
    datePicker.minuteInterval = minuteInterval;

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Calculate the proper date value (and the date to be selected on the UI
    // display) by rounding down to the nearest minute interval.
    NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSMinuteCalendarUnit fromDate:date];
    NSInteger minutes = [dateComponents minute];
    NSInteger minutesRounded = ( (NSInteger)(minutes / minuteInterval) ) * minuteInterval;
    NSDate *roundedDate = [[NSDate alloc] initWithTimeInterval:60.0 * (minutesRounded - minutes) sinceDate:date];

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Set the date picker's value (and the selected date on the UI display) to
    // the rounded date.
    if ([roundedDate isEqualToDate:datePicker.date])
    {
        // We need to set the date picker's value to something different than
        // the rounded date, because the second call to set the date picker's
        // date with the same value is ignored. Which could be bad since the
        // call above to set the date picker's minute interval can leave the
        // date picker with the wrong selected date (the whole reason why we are
        // doing this).
        NSDate *diffrentDate = [[NSDate alloc] initWithTimeInterval:60 sinceDate:roundedDate];
        datePicker.date = diffrentDate;
        [diffrentDate release];
    }
    datePicker.date = roundedDate;
    [roundedDate release];
}

Обратите внимание на ту часть, где дата UIDatePicker установлена ​​дважды. Было забавно понять это.

Кто-нибудь знает, как отключить анимацию для звонка на minuteInterval? Фантомная прокрутка при нажатии 5 на 10 немного неприглядна.

6 голосов
/ 21 февраля 2013

Я использовал приведенное выше решение от mmoris и создал метод, который возвращает округленную дату .. (для ARC)

- (NSDate *)getRoundedDate:(NSDate *)inDate{

    NSDate *returnDate;
    NSInteger minuteInterval = 10;
    NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSMinuteCalendarUnit fromDate:inDate];
    NSInteger minutes = [dateComponents minute];
    NSInteger minutesRounded = ( (NSInteger)(minutes / minuteInterval) ) * minuteInterval;
    NSDate *roundedDate = [[NSDate alloc] initWithTimeInterval:60.0 * (minutesRounded - minutes) sinceDate:inDate];

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Set the date picker's value (and the selected date on the UI display) to
    // the rounded date.
    if ([roundedDate isEqualToDate:inDate])
    {
       // We need to set the date picker's value to something different than
       // the rounded date, because the second call to set the date picker's
       // date with the same value is ignored. Which could be bad since the
       // call above to set the date picker's minute interval can leave the
       // date picker with the wrong selected date (the whole reason why we are
       // doing this).
       NSDate *diffrentDate = [[NSDate alloc] initWithTimeInterval:60 sinceDate:roundedDate];
       returnDate = diffrentDate;
       //[diffrentDate release];
    }

    returnDate = roundedDate;
    return returnDate;
}
4 голосов
/ 26 июля 2013

Вот еще один подход с категорией Objective-C!

Я взял дух поведения округления @ zurbergram (вверх / вниз до ближайшего) и общий ответ @ mmorris и придумал эту категорию:

#import <UIKit/UIKit.h>

@interface UIDatePicker (SetDateRounded)

-(void)setMinimumDateRoundedByMinuteInterval:(NSDate *)minimumDate;
-(void)setDateRoundedByMinuteInterval:(NSDate *)date animated:(BOOL)animatedYesNo;

@end

@implementation UIDatePicker (SetDateRounded)

-(void)setDateRoundedByMinuteInterval:(NSDate *)date animated:(BOOL)animatedYesNo
{
    NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSMinuteCalendarUnit fromDate:date];
    NSInteger minutes = [dateComponents minute];
    NSInteger minutesRounded = roundf((float)minutes / (float)[self minuteInterval]) * self.minuteInterval;
    NSDate *roundedDate = [[NSDate alloc] initWithTimeInterval:60.0 * (minutesRounded - minutes) sinceDate:date];
    [self setDate:roundedDate animated:animatedYesNo];
}

-(void)setMinimumDateRoundedByMinuteInterval:(NSDate *)date
{
    NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSMinuteCalendarUnit fromDate:date];
    NSInteger minutes = [dateComponents minute];
    NSInteger minutesRounded = roundf((float)minutes / (float)[self minuteInterval]) * self.minuteInterval;
    NSDate *roundedDate = [[NSDate alloc] initWithTimeInterval:60.0 * (minutesRounded - minutes) sinceDate:date];
    [self setMinimumDate:roundedDate];
}

@end

Тогда в вашей реализации вы можете сделать что-то вроде этого:

#import "UIDatePicker+SetDateRounded.h"

...

- (void)viewDidLoad
{
    [super viewDidLoad];

    _datePicker.minuteInterval = 15;

    [_datePicker setMinimumDateRoundedByMinuteInterval:[NSDate date]];
    [_datePicker setDateRoundedByMinuteInterval:[NSDate date] animated:YES];
}

...

Бонусный метод: setMinimumDateRoundedByMinuteInterval: позволяет установить начальный минимум средства выбора, чтобы он соответствовал тому же поведению. Один из рефакторингов - абстрагировать фактическую часть вычислений в собственный метод вместо копийной пасты, но я уверен, что люди могут оптимизировать это для себя.

3 голосов
/ 11 июня 2013

Вот обновленная версия getRoundedDate: она округляется вверх или вниз, так что 13:03 округляется до 13:00, а 13:12 округляется до 13:15 * 1001.

-(NSDate *)getRoundedDate:(NSDate *)inDate
{
    NSInteger minuteInterval = 15;
    NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSMinuteCalendarUnit fromDate:inDate];
    NSInteger minutes = [dateComponents minute];

    float minutesF = [[NSNumber numberWithInteger:minutes] floatValue];
    float minuteIntervalF = [[NSNumber numberWithInteger:minuteInterval] floatValue];

    // Determine whether to add 0 or the minuteInterval to time found by rounding down
    NSInteger roundingAmount = (fmodf(minutesF, minuteIntervalF)) > minuteIntervalF/2.0 ? minuteInterval : 0;
    NSInteger minutesRounded = ( (NSInteger)(minutes / minuteInterval) ) * minuteInterval;
    NSDate *roundedDate = [[NSDate alloc] initWithTimeInterval:60.0 * (minutesRounded + roundingAmount - minutes) sinceDate:inDate];

    return roundedDate;
}
1 голос
/ 02 октября 2017

Swift 4 версия

func round(date: Date, for minuteInterval: Int) -> Date {
        let dateComponents = Calendar.current.dateComponents([.minute], from: date)

        let minutes = dateComponents.minute!

        // Determine whether to add 0 or the minuteInterval to time found by rounding down

        let intervalRemainder = Double(minutes).truncatingRemainder(
            dividingBy: Double(minuteInterval)
        )

        let halfInterval = Double(minuteInterval) / 2.0

        let roundingAmount: Int
        if  intervalRemainder > halfInterval {
            roundingAmount = minuteInterval
        } else {
            roundingAmount = 0
        }

        let minutesRounded = minutes / minuteInterval * minuteInterval
        let timeInterval = TimeInterval(
            60 * (minutesRounded + roundingAmount - minutes)
        )

        let roundedDate = Date(timeInterval: timeInterval, since: date)

        return roundedDate
    }
1 голос
/ 27 июля 2015

zurbergram код в Swift :

func getRoundedDate(inDate: NSDate) -> NSDate {

    let minuteInterval = 15
    let dateComponents = NSCalendar.currentCalendar().components(NSCalendarUnit.MinuteCalendarUnit, fromDate: inDate)
    let minutes = dateComponents.minute

    let minutesF = NSNumber(integer: minutes).floatValue
    let minuteIntervalF = NSNumber(integer: minuteInterval).floatValue

    // Determine whether to add 0 or the minuteInterval to time found by rounding down
    let roundingAmount = (fmodf(minutesF, minuteIntervalF)) > minuteIntervalF/2.0 ? minuteInterval : 0
    let minutesRounded = (minutes / minuteInterval) * minuteInterval
    let timeInterval = NSNumber(integer: (60 * (minutesRounded + roundingAmount - minutes))).doubleValue
    let roundedDate = NSDate(timeInterval: timeInterval, sinceDate: inDate )

    return roundedDate
}
1 голос
/ 10 декабря 2011

У меня была такая же проблема с UIDatePicker только с часами и минутами, каждый раз, когда я выбирал время, добавляющее 20 минут в пользовательский интерфейс, а не выбранное время. В моем случае решение было довольно простым, установите picker.minuteInterval=5 перед настройкой значения средства выбора.

Надеюсь, это поможет другим людям.

...