2

Ive been trying to modify the repository, https://github.com/PeteC/DSLCalendarView to allow a user to select a start and end date by tapping and automatically selecting the dates in between. I achieved this by implementing the following code in the demo accompanied:

The issue is it breaks the original implementation of dragging over calendar to select date range.

Any help/guidance is greatly appreciated, also if you are aware of any other library that achieves the same, I'll be really grateful. The functionality I am looking for is: Allow user to select first date, last date and show the dates in the middle as selection.

in ViewController.m

- (DSLCalendarRange*)calendarView:(DSLCalendarView *)calendarView didDragToDay:(NSDateComponents *)day selectingRange:(DSLCalendarRange *)range {

    if (!self.startDate) {

        //        self.startDate = [self.dateFormatter dateFromString:[NSString stringWithFormat:@"%d/%d/%d",range.startDay.month, range.startDay.day,range.startDay.year]];
        //self.startDate = range.s

        self.startDate = range.startDay;

        self.hasSelectedStartDate = YES;

        return [[DSLCalendarRange alloc] initWithStartDay:self.startDate endDay:self.startDate];



        NSLog(@"start date set to: %@",self.startDate);
    }
    else if (self.startDate && !self.endDate)
    {

        //self.endDate = [self.dateFormatter dateFromString:[NSString stringWithFormat:@"%d/%d/%d",range.startDay.month, range.startDay.day,range.startDay.year]];
        self.endDate = range.endDay;

        NSLog(@"Start date is: %@",self.startDate);
        NSLog(@"end date set to: %@",self.endDate);

        return [[DSLCalendarRange alloc] initWithStartDay:self.startDate endDay:self.endDate];


    } else if (self.startDate && self.endDate)
    {
        return [[DSLCalendarRange alloc] initWithStartDay:self.startDate endDay:self.endDate];

        self.hasSelectedStartDate = NO;
    }
    NSLog(@"Select range programattically");

}

In DSLCalendarView.m, I have added the following piece of code to touchedEnded to complement the above implementation:

//added code: Aakash
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
//added condition here besides the other code
if ([self.delegate respondsToSelector:@selector(hasSelectedStartDate)]) {
    self.flag = [self.delegate performSelector:@selector(hasSelectedStartDate)];
    NSLog(@"Value: %hhd",self.flag);
}

if (!self.draggedOffStartDay && [self.draggingStartDay isEqual:touchedView.day] && !self.flag) {
self.selectedRange = [[DSLCalendarRange alloc] initWithStartDay:touchedView.day endDay:touchedView.day];
}

Original code for touch handling is:

#pragma mark - Touches

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    DSLCalendarDayView *touchedView = [self dayViewForTouches:touches];
    if (touchedView == nil) {
        self.draggingStartDay = nil;
        return;
    }

    self.draggingStartDay = touchedView.day;
    self.draggingFixedDay = touchedView.day;
    self.draggedOffStartDay = NO;

    DSLCalendarRange *newRange = self.selectedRange;
    if (self.selectedRange == nil) {
        newRange = [[DSLCalendarRange alloc] initWithStartDay:touchedView.day endDay:touchedView.day];
    }
    else if (![self.selectedRange.startDay isEqual:touchedView.day] && ![self.selectedRange.endDay isEqual:touchedView.day]) {
        newRange = [[DSLCalendarRange alloc] initWithStartDay:touchedView.day endDay:touchedView.day];
    }
    else if ([self.selectedRange.startDay isEqual:touchedView.day]) {
        self.draggingFixedDay = self.selectedRange.endDay;
    }
    else {
        self.draggingFixedDay = self.selectedRange.startDay;
    }

    if ([self.delegate respondsToSelector:@selector(calendarView:didDragToDay:selectingRange:)]) {
    newRange = [self.delegate calendarView:self didDragToDay:touchedView.day selectingRange:newRange];
    }
    self.selectedRange = newRange;

    [self positionCalloutViewForDayView:touchedView]; }

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    if (self.draggingStartDay == nil) {
        return;
    }

    DSLCalendarDayView *touchedView = [self dayViewForTouches:touches];
    if (touchedView == nil) {
        self.draggingStartDay = nil;
        return;
    }

    DSLCalendarRange *newRange;
    if ([touchedView.day.date compare:self.draggingFixedDay.date] == NSOrderedAscending) {
        newRange = [[DSLCalendarRange alloc] initWithStartDay:touchedView.day endDay:self.draggingFixedDay];
    }
    else {
        newRange = [[DSLCalendarRange alloc] initWithStartDay:self.draggingFixedDay endDay:touchedView.day];
    }

    if ([self.delegate respondsToSelector:@selector(calendarView:didDragToDay:selectingRange:)]) {
        newRange = [self.delegate calendarView:self didDragToDay:touchedView.day selectingRange:newRange];
    }
    self.selectedRange = newRange;

    if (!self.draggedOffStartDay) {
        if (![self.draggingStartDay isEqual:touchedView.day]) {
            self.draggedOffStartDay = YES;
        }
    }

    [self positionCalloutViewForDayView:touchedView]; }

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    if (self.draggingStartDay == nil) {
        return;
    }

    DSLCalendarDayView *touchedView = [self dayViewForTouches:touches];
    if (touchedView == nil) {
        self.draggingStartDay = nil;
        return;
    }


    if (!self.draggedOffStartDay && [self.draggingStartDay isEqual:touchedView.day]) {
    self.selectedRange = [[DSLCalendarRange alloc] initWithStartDay:touchedView.day endDay:touchedView.day];
    }

    self.draggingStartDay = nil;

    // Check if the user has dragged to a day in an adjacent month
    if (touchedView.day.year != _visibleMonth.year || touchedView.day.month != _visibleMonth.month) {
        // Ask the delegate if it's OK to animate to the adjacent month
        BOOL animateToAdjacentMonth = YES;
        if ([self.delegate respondsToSelector:@selector(calendarView:shouldAnimateDragToMonth:)]) {
            animateToAdjacentMonth = [self.delegate calendarView:self shouldAnimateDragToMonth:[touchedView.dayAsDate dslCalendarView_monthWithCalendar:_visibleMonth.calendar]];
        }

        if (animateToAdjacentMonth) {
            if ([touchedView.dayAsDate compare:_visibleMonth.date] == NSOrderedAscending) {
                [self didTapMonthBack:nil];
            }
            else {
                [self didTapMonthForward:nil];
            }
        }
    }

    if ([self.delegate respondsToSelector:@selector(calendarView:didSelectRange:)]) {
        [self.delegate calendarView:self didSelectRange:self.selectedRange];
    }
     }
ManicMonkOnMac
  • 1,476
  • 13
  • 21
  • i need way to add event and display event to this library , any suggestion – Mina Fawzy Oct 12 '14 at 14:13
  • @ManicMonkOnMac have you implemented this..? Can you share the code sureshdit45@gmail.com –  Jan 22 '15 at 16:36
  • Yeah I did implement it,i'll try and post the relevant code, or I can send you the whole modified framework on your mail id. It was more than an ear ago though :/ – ManicMonkOnMac Jan 23 '15 at 18:22
  • @ManicMonkOnMac How do I set some dates to be selected when the calender is started. Like marking dates randomly in a month. – Augustin Jose Apr 21 '15 at 12:24

3 Answers3

1

You can achieve this from the demo example it self,

just look for - (void)calendarView:(DSLCalendarView *)calendarView didSelectRange:(DSLCalendarRange *)range delegate in ViewController.m

replace it with this,

- (void)calendarView:(DSLCalendarView *)calendarView didSelectRange:(DSLCalendarRange *)range
{
    if (range != nil)
    {
    if(startD == nil) {
        if(endD == nil) {
            [self start:nil];
        }else{
            [self end:nil];
        }
    }else{
        if(endD == nil) {
            [self end:nil];
        }
    }

    [self draw:nil];
}

Now look for - (IBAction)draw:(id)sender in the same class,

replace it with this,

- (IBAction)draw:(id)sender {
    if(startD && endD) {
        [_calendarView setSelectedRange:[[DSLCalendarRange alloc] initWithStartDay:startD.startDay endDay:endD.endDay]];
        startD = nil;
        endD = nil;
    }

    else if(startD)
    [_calendarView setSelectedRange:[[DSLCalendarRange alloc] initWithStartDay:startD.startDay endDay:startD.endDay]];
    else if(endD)
        [_calendarView setSelectedRange:[[DSLCalendarRange alloc] initWithStartDay:endD.startDay endDay:endD.endDay]];
}
Hemang
  • 26,840
  • 19
  • 119
  • 186
0

CXDurationPicker is the most close component I can find to expedia calendar. https://github.com/concurlabs/CXDurationPicker

To mimic expedia calendar picker's behaviour, I also did some changes.

  1. define own delegate

    #pragma mark - CXDurationPickerViewDelegate
    - (void)durationPicker:(CXDurationPickerView *)durationPicker
            endDateChanged:(CXDurationPickerDate)pickerDate {
    
        NSDate *firstDate = [CXDurationPickerUtils dateFromPickerDate:durationPicker.startDate];
        NSDate *secondDate = [CXDurationPickerUtils dateFromPickerDate:durationPicker.endDate];
    
        [self updateStartDate:firstDate EndDate:secondDate AtIndex:selectedId];
        [self dismissDatePicker:nil];
    }
    
    - (void)durationPicker:(CXDurationPickerView *)durationPicker
         singleDateChanged:(CXDurationPickerDate)pickerDate {
        NSLog(@"singleDateChanged");
    }
    
    - (void)durationPicker:(CXDurationPickerView *)durationPicker
          startDateChanged:(CXDurationPickerDate)pickerDate {
    
        durationPicker.endDate = durationPicker.startDate;
        durationPicker.mode = CXDurationPickerModeEndDate;
    }
    
    #pragma mark - CXDurationPickerViewDelegate Optionals
    
    - (void)durationPicker:(CXDurationPickerView *)durationPicker
    invalidEndDateSelected:(CXDurationPickerDate)date {
    
        NSLog(@"Invalid end date selected.");
    
        durationPicker.startDate = durationPicker.endDate = date;
        durationPicker.mode = CXDurationPickerModeEndDate;
    }
    
    - (void)durationPicker:(CXDurationPickerView *)durationPicker invalidStartDateSelected:(CXDurationPickerDate)date {
        NSLog(@"Invalid start date selected.");
        durationPicker.startDate = durationPicker.endDate = date;
        durationPicker.mode = CXDurationPickerModeEndDate;
    }
    
    - (void)durationPicker:(CXDurationPickerView *)durationPicker
       didSelectDateInPast:(CXDurationPickerDate)date
                   forMode:(CXDurationPickerMode)mode {
    
        NSLog(@"Date was selected in the past. Ignoring.");
    }
    
  2. Update code in CXDurationPickerDayView.m to fix display problem when start day and end day is the same day.

    --- a/Popsoc/CXDurationPicker/CXDurationPickerView.m
    +++ b/Popsoc/CXDurationPicker/CXDurationPickerView.m
    @@ -482,8 +482,12 @@
         day.type = CXDurationPickerDayTypeStart;
    
         day = (CXDurationPickerDayView *) [self.days lastObject];
    
         day.type = CXDurationPickerDayTypeEnd;
    +
    +    if (self.days.count == 1) {
    +        day.type = CXDurationPickerDayTypeSingle;
    +    }
    
River2202
  • 1,225
  • 13
  • 22
0

I modified the DSLCalendarView library and it works like charm in both ways, 1. Select duration dragging 2. Expedia-like tapping.

In DSLCalendarView.m file, find - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; method. In this method, you'll find below if statement

if (!self.draggedOffStartDay && [self.draggingStartDay isEqual:touchedView.day]) {
    // comment out this original code
    // self.selectedRange = [[DSLCalendarRange alloc] initWithStartDay:touchedView.day endDay:touchedView.day];

    // add below code block
    if (!self.startDate) {
        self.startDate = self.draggingStartDay;
        self.selectedRange = [[DSLCalendarRange alloc] initWithStartDay:self.draggingStartDay
                                                                 endDay:touchedView.day];
    }
    else if (self.startDate && !self.endDate) {
        self.endDate = touchedView.day;
        self.selectedRange = [[DSLCalendarRange alloc] initWithStartDay:self.startDate
                                                                 endDay:touchedView.day];
    }
    else if (self.startDate && self.endDate) {
        self.startDate = self.draggingStartDay;
        self.endDate = nil;
        self.selectedRange = [[DSLCalendarRange alloc] initWithStartDay:self.draggingStartDay
                                                                 endDay:touchedView.day];
    }
}

Don't forget to declare two global vars @property (nonatomic, strong) NSDateComponents *startDate; @property (nonatomic, strong) NSDateComponents *endDate; in the same file.

You can check the result by implementing below delegate method in your VC.

#pragma mark - DSLCalendarViewDelegate methods

- (void)calendarView:(DSLCalendarView *)calendarView didSelectRange:(DSLCalendarRange *)range {
    if (range) {
        NSLog(@"selected %ld/%ld/%ld - %ld/%ld/%ld", range.startDay.year, range.startDay.month, range.startDay.day, range.endDay.year, range.endDay.month, range.endDay.day);
    }
}
Vincent Gigandet
  • 918
  • 10
  • 21