|
@interface MealPlanScrollView () { |
|
NSMutableArray *_dayViews; |
|
NSInteger _numberOfDays; |
|
|
|
NSInteger _viewFirstDateNum; |
|
NSInteger _viewLastDateNum; |
|
NSInteger _todayDateNum; |
|
NSInteger _maxNumberOfDays; |
|
} |
|
|
|
|
|
- (void)buildDayViews; |
|
- (MealsDateView *)insertDayAtIndex:(NSInteger)index; |
|
- (CGFloat)deleteDay:(MealsDateView *)day; |
|
- (void)adjustDayOriginsBy:(CGFloat)distance startingAt:(NSInteger)start; |
|
|
|
- (MealsDateView *)findViewForDateNum:(NSInteger)dateNum; |
|
|
|
- (void)setTodayRow; |
|
|
|
@end |
|
|
|
|
|
|
|
@implementation MealPlanScrollView |
|
|
|
- (id)initWithFrame:(CGRect)frame { |
|
self = [super initWithFrame:frame]; |
|
if (self) { |
|
// Base the number of days on the device size |
|
// use a week either side to allow for smooth scrolling when moving by 7 days |
|
_numberOfDays = roundf([UIScreen portraitHeight] / DEFAULT_ROW_HEIGHT) + 14; |
|
_maxNumberOfDays = [NSDate maxNumberOfDates]; |
|
|
|
|
|
[self setContentSize:CGSizeMake(CGRectGetWidth(frame), 0)]; |
|
[self setDelegate:self]; |
|
|
|
[self setTodayRow]; |
|
_viewFirstDateNum = _todayDateNum - _numberOfDays/2; |
|
_viewLastDateNum = _todayDateNum + _numberOfDays/2; |
|
|
|
|
|
// Build the day views |
|
_dayViews = [[NSMutableArray alloc] init]; |
|
[self buildDayViews]; |
|
[self setYear]; |
|
|
|
MealsDateView *firstVisible = _dayViews[(_numberOfDays/2)-1]; |
|
[self setContentOffset:CGPointMake(0, CGRectGetMinY([firstVisible frame]))]; |
|
} |
|
return self; |
|
} |
|
|
|
|
|
|
|
- (void)layoutSubviews { |
|
[super layoutSubviews]; |
|
|
|
// Adjust the scroll offset if gone too far one way or the other |
|
CGPoint currentOffset = [self contentOffset]; |
|
currentOffset.x = 0; |
|
CGFloat contentHeight = [self contentSize].height; |
|
CGFloat centerOffsetY = (contentHeight - CGRectGetHeight([self bounds])) / 2.0f; |
|
CGFloat distanceFromCenter = currentOffset.y - centerOffsetY; |
|
CGFloat buffer = DEFAULT_ROW_HEIGHT; |
|
|
|
if (fabs(distanceFromCenter) > buffer) { |
|
if (distanceFromCenter < 0) { |
|
// move from bottom to top |
|
while (fabs(distanceFromCenter) > buffer) { |
|
if (_viewFirstDateNum == 0) { |
|
break; |
|
} |
|
_viewFirstDateNum--; |
|
_viewLastDateNum--; |
|
|
|
[self deleteDay:[_dayViews lastObject]]; |
|
|
|
MealsDateView *firstDay = [self insertDayAtIndex:0]; |
|
|
|
CGFloat h = CGRectGetHeight([firstDay frame]); |
|
currentOffset.y += h; |
|
|
|
[self adjustDayOriginsBy:h startingAt:1]; |
|
|
|
distanceFromCenter += h; |
|
if (distanceFromCenter >= 0) { |
|
break; |
|
} |
|
} |
|
|
|
} else { |
|
// move from top to bottom |
|
while (distanceFromCenter > buffer) { |
|
if ((_viewLastDateNum-1) == _maxNumberOfDays) { |
|
break; |
|
} |
|
_viewFirstDateNum++; |
|
_viewLastDateNum++; |
|
|
|
CGFloat h = [self deleteDay:_dayViews[0]]; |
|
|
|
[self insertDayAtIndex:[_dayViews count]]; |
|
[self adjustDayOriginsBy:-1*h startingAt:0]; |
|
|
|
currentOffset.y -= h; |
|
distanceFromCenter -= h; |
|
if (distanceFromCenter <= 0) { |
|
break; |
|
} |
|
} |
|
} |
|
|
|
} |
|
|
|
[self setContentOffset:currentOffset]; |
|
} |
|
|
|
|
|
|
|
|
|
#pragma mark - Public |
|
|
|
- (void)changeRowHeightOfDate:(NSDate *)date by:(CGFloat)delta { |
|
NSInteger dateNum = [date daysSinceStart]; |
|
MealsDateView *day = [self findViewForDateNum:dateNum]; |
|
if (!day) { |
|
return; |
|
} |
|
|
|
|
|
CGFloat oldHeight = CGRectGetHeight([day frame]); |
|
|
|
// Adjust the size of the views |
|
CGFloat newHeight = oldHeight + delta; |
|
if (newHeight != oldHeight) { |
|
CGFloat diff = newHeight - oldHeight; |
|
|
|
CGSize s = [self contentSize]; |
|
s.height += diff; |
|
[self setContentSize:s]; |
|
|
|
[self adjustDayOriginsBy:diff startingAt:(dateNum-_viewFirstDateNum+1)]; |
|
} |
|
|
|
} |
|
|
|
|
|
- (void)layoutMeals { |
|
CGRect f = [self frame]; |
|
f.size.width = [UIScreen width]; |
|
f.size.height = [UIScreen height]-[UIScreen statusBarHeight]-f.origin.y; |
|
[self setFrame:f]; |
|
|
|
CGFloat y = 0; |
|
|
|
for (MealsDateView *day in _dayViews) { |
|
CGRect df = [day frame]; |
|
if (CGRectGetMinX(df) != y) { |
|
df.origin.y = y; |
|
[day setFrame:df]; |
|
} |
|
|
|
[day layoutMeals]; |
|
|
|
y += CGRectGetHeight([day frame]); |
|
} |
|
|
|
CGSize s = [self contentSize]; |
|
s.height = y; |
|
[self setContentSize:s]; |
|
} |
|
|
|
|
|
#pragma mark - Meal actions |
|
|
|
- (void)scrollToDate:(NSDate *)date inRect:(CGRect)rect { |
|
BOOL reload = NO; |
|
|
|
NSInteger newDateNum = [date daysSinceStart]; |
|
if ((newDateNum <= _viewFirstDateNum) || (newDateNum >= _viewLastDateNum)) { |
|
_viewFirstDateNum = newDateNum - _numberOfDays/2; |
|
_viewLastDateNum = newDateNum + _numberOfDays/2; |
|
|
|
if (_viewFirstDateNum < 0) { |
|
_viewFirstDateNum = 0; |
|
_viewLastDateNum = _viewFirstDateNum + _numberOfDays; |
|
} |
|
|
|
if (_viewLastDateNum > [NSDate maxNumberOfDates]) { |
|
_viewLastDateNum = [NSDate maxNumberOfDates]; |
|
_viewFirstDateNum = _viewLastDateNum - _numberOfDays; |
|
} |
|
|
|
reload = YES; |
|
} |
|
if (reload) { |
|
[self buildDayViews]; |
|
} |
|
|
|
NSInteger newRow = newDateNum - _viewFirstDateNum; |
|
if (newRow < 0) { |
|
newRow = 0; |
|
} |
|
if (newRow >= [_dayViews count]) { |
|
newRow = [_dayViews count] - 1; |
|
} |
|
|
|
MealsDateView *day = _dayViews[newRow]; |
|
CGRect f = [self frame]; |
|
CGRect df = [day frame]; |
|
CGPoint offset = [self contentOffset]; |
|
// rect is relative to the screen |
|
offset.y = df.origin.y - (rect.origin.y - f.origin.y); |
|
if (offset.y < 0) { |
|
offset.y = 0; |
|
} |
|
|
|
[CATransaction begin]; |
|
[self setContentOffset:offset animated:!reload]; |
|
[CATransaction commit]; |
|
|
|
} |
|
|
|
|
|
- (void)scrollByDistance:(CGFloat)distance { |
|
CGPoint offset = [self contentOffset]; |
|
offset.y -= distance; |
|
[UIView animateWithDuration:IOS_ANIMATION_DURATION |
|
delay:0.0f |
|
options:IOS_ANIMATION_CURVE |
|
animations:^{ |
|
[self setContentOffset:offset animated:NO]; |
|
} |
|
completion:^(BOOL finished) { |
|
if (finished) { |
|
} |
|
}]; |
|
} |
|
|
|
|
|
|
|
#pragma mark - Local |
|
|
|
- (void)buildDayViews { |
|
for (MealsDateView *day in _dayViews) { |
|
[day removeFromSuperview]; |
|
} |
|
[_dayViews removeAllObjects]; |
|
CGSize s = [self contentSize]; |
|
s.height = 0; |
|
[self setContentSize:s]; |
|
|
|
|
|
for (int i=0; i<_numberOfDays; i++) { |
|
[self insertDayAtIndex:i]; |
|
} |
|
} |
|
|
|
|
|
- (MealsDateView *)insertDayAtIndex:(NSInteger)index { |
|
NSInteger dayNum = (_viewFirstDateNum + index); |
|
NSDate *thisDate = [NSDate dateSinceStartForDayNumber:dayNum]; |
|
|
|
CGFloat y = 0; |
|
if (index > 0) { |
|
UIView *prevDay = _dayViews[(index-1)]; |
|
y = CGRectGetMaxY([prevDay frame]); |
|
} |
|
|
|
DateRowType rowType = DateRowTypeMiddle; |
|
if (dayNum == 0) { |
|
rowType = DateRowTypeFirst; |
|
} else if (dayNum == _maxNumberOfDays) { |
|
rowType = DateRowTypeLast; |
|
} |
|
|
|
|
|
MealsDateView *day = [[MealsDateView alloc] initWithDate:thisDate rowType:rowType]; |
|
CGRect f = [day frame]; |
|
f.origin.y = y; |
|
[day setFrame:f]; |
|
[_dayViews insertObject:day atIndex:index]; |
|
[self insertSubview:day atIndex:0]; |
|
|
|
CGSize s = [self contentSize]; |
|
s.height += CGRectGetHeight([day frame]); |
|
[self setContentSize:s]; |
|
|
|
return day; |
|
} |
|
|
|
|
|
- (CGFloat)deleteDay:(MealsDateView *)day { |
|
CGRect f = [day frame]; |
|
|
|
CGSize s = [self contentSize]; |
|
s.height -= CGRectGetHeight(f); |
|
[self setContentSize:s]; |
|
[day removeFromSuperview]; |
|
|
|
[_dayViews removeObject:day]; |
|
|
|
return CGRectGetHeight(f); |
|
} |
|
|
|
|
|
- (void)adjustDayOriginsBy:(CGFloat)distance startingAt:(NSInteger)start { |
|
for (int i=start; i<[_dayViews count]; i++) { |
|
MealsDateView *day = (MealsDateView *)_dayViews[i]; |
|
CGRect f = [day frame]; |
|
f.origin.y += distance; |
|
[day setFrame:f]; |
|
} |
|
} |
|
|
|
|
|
- (MealsDateView *)findViewForDateNum:(NSInteger)dateNum { |
|
if ((dateNum < _viewFirstDateNum) || (dateNum > _viewLastDateNum)) { |
|
return nil; |
|
} |
|
|
|
NSInteger index = dateNum - _viewFirstDateNum; |
|
if ((index >= 0) && (index < [_dayViews count])) { |
|
return _dayViews[index]; |
|
} else { |
|
return nil; |
|
} |
|
} |
|
|
|
|
|
#pragma mark - Date/time methods |
|
|
|
- (void)setTodayRow { |
|
_todayDateNum = [[NSDate today] daysSinceStart]; |
|
} |