Ответ 1
После большого количества R & D у меня есть ответ для вас, и ответ: -
RSDayFlow, который разработан с использованием DayFlow Я проделал большую часть этой части, и я рекомендую, если вы хотите сделать приложение для календаря, используйте библиотеку DayFlow, ее хорошее.
Теперь мы подошли к тому, как они справились с бесконечным потоком, и доверяют мне, мой друг, мне потребовалось некоторое время, чтобы понять это, эти ребята действительно продумали это, строя это!
1.) Во-первых, они начали с создания структуры, в RSDayFlow.h
typedef struct {
NSUInteger year;
NSUInteger month;
NSUInteger day;
} RSDFDatePickerDate;
это используется для сохранения двух свойств
@property (nonatomic, readonly, assign) RSDFDatePickerDate fromDate;
@property (nonatomic, readonly, assign) RSDFDatePickerDate toDate;
в RSDFDatePickerView
, который представляет собой представление, которое содержит UICollectionView (подклассифицировано в RSDFDatePickerCollectionView) и все остальное, видимое на экране (кроме навигационной панели и TabBar курса). RSDFDatePickerView инициализируется из RSDFDatePickerViewController
с теми же границами обзора, что и в ViewController.
Теперь, как следует из названия, fromDate и toDate используются в качестве диапазона для отображения календаря. Первоначально это fromDate и toDate вычисляется как -6 месяцев и +6 месяцев с текущей даты соответственно, также текущая дата устанавливается в RSDFDatePickerViewController, которая сама вызывает следующий метод:
[self.datePickerView selectDate:today];
Теперь при инициализации следующего метода вызывается в RSDFDatePickerView
- (void)commonInitializer
{
NSDateComponents *nowYearMonthComponents = [self.calendar components:(NSCalendarUnitYear | NSCalendarUnitMonth) fromDate:[NSDate date]];
NSDate *now = [self.calendar dateFromComponents:nowYearMonthComponents];
_fromDate = [self pickerDateFromDate:[self.calendar dateByAddingComponents:((^{
NSDateComponents *components = [NSDateComponents new];
components.month = -6;
return components;
})()) toDate:now options:0]];
_toDate = [self pickerDateFromDate:[self.calendar dateByAddingComponents:((^{
NSDateComponents *components = [NSDateComponents new];
components.month = 6;
return components;
})()) toDate:now options:0]];
NSDateComponents *todayYearMonthDayComponents = [self.calendar components:(NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay) fromDate:[NSDate date]];
_today = [self.calendar dateFromComponents:todayYearMonthDayComponents];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(significantTimeChange:)
name:UIApplicationSignificantTimeChangeNotification
object:nil];
}
И теперь еще одна важная вещь, при назначении текущей даты, то есть сегодняшней даты, также определяется индексный путь текущего элемента ячейки CollectionView, посмотрите на функцию, называемую ранее:
- (void)selectDate:(NSDate *)date
{
if (![self.selectedDate isEqual:date]) {
if (self.selectedDate &&
[self.selectedDate compare:[self dateFromPickerDate:self.fromDate]] != NSOrderedAscending &&
[self.selectedDate compare:[self dateFromPickerDate:self.toDate]] != NSOrderedDescending) {
NSIndexPath *previousSelectedCellIndexPath = [self indexPathForDate:self.selectedDate];
[self.collectionView deselectItemAtIndexPath:previousSelectedCellIndexPath animated:NO];
UICollectionViewCell *previousSelectedCell = [self.collectionView cellForItemAtIndexPath:previousSelectedCellIndexPath];
if (previousSelectedCell) {
[previousSelectedCell setNeedsDisplay];
}
}
_selectedDate = date;
if (self.selectedDate &&
[self.selectedDate compare:[self dateFromPickerDate:self.fromDate]] != NSOrderedAscending &&
[self.selectedDate compare:[self dateFromPickerDate:self.toDate]] != NSOrderedDescending) {
NSIndexPath *indexPathForSelectedDate = [self indexPathForDate:self.selectedDate];
[self.collectionView selectItemAtIndexPath:indexPathForSelectedDate animated:NO scrollPosition:UICollectionViewScrollPositionNone];
UICollectionViewCell *selectedCell = [self.collectionView cellForItemAtIndexPath:indexPathForSelectedDate];
if (selectedCell) {
[selectedCell setNeedsDisplay];
}
}
}
}
Итак, как можно догадаться, текущий раздел оказывается равным 6, т.е. Месяцу и элементу ячейки нет. это день.
Уф! что он, выше был основным обзором, для нас, чтобы понять бесконечный свиток, вот оно...
2.) Наш субкласс UICollectionView, то есть RSDFDatePickerCollectionView Переопределяет
- (void)layoutSubviews;
метода UICollectionView (вызванного методом layoutIfNeeded автоматически). Теперь у нас есть протокол, определенный в нашем RSDFDatePickerCollectionView.
@protocol RSDFDatePickerCollectionViewDelegate <UICollectionViewDelegate>
///---------------------------------
/// @name Supporting Layout Subviews
///---------------------------------
/**
Tells the delegate that the collection view will layout subviews.
@param pickerCollectionView The collection view which will layout subviews.
*/
- (void) pickerCollectionViewWillLayoutSubviews:(RSDFDatePickerCollectionView *)pickerCollectionView;
@end
этот делегат вызывается из - (void)layoutSubviews;
в CollectionView и реализован в RSDFDatePickerView.m
Эй! Почему бы вам не сразу разобраться?
: - | Я собираюсь, просто повесить там, хорошо!
Итак, как я объяснял, следующая реализация RSDFDatePickerCollectionViewDelegate в RSDFDatePickerView.m
#pragma mark - RSDFDatePickerCollectionViewDelegate
- (void)pickerCollectionViewWillLayoutSubviews:(RSDFDatePickerCollectionView *)pickerCollectionView
{
// Note: relayout is slower than calculating 3 or 6 months’ worth of data at a time
// So we punt 6 months at a time.
// Running Time Self Symbol Name
//
// 1647.0ms 23.7% 1647.0 objc_msgSend
// 193.0ms 2.7% 193.0 -[NSIndexPath compare:]
// 163.0ms 2.3% 163.0 objc::DenseMap<objc_object*, unsigned long, true, objc::DenseMapInfo<objc_object*>, objc::DenseMapInfo<unsigned long> >::LookupBucketFor(objc_object* const&, std::pair<objc_object*, unsigned long>*&) const
// 141.0ms 2.0% 141.0 DYLD-STUB$$-[_UIHostedTextServiceSession dismissTextServiceAnimated:]
// 138.0ms 1.9% 138.0 -[NSObject retain]
// 136.0ms 1.9% 136.0 -[NSIndexPath indexAtPosition:]
// 124.0ms 1.7% 124.0 -[_UICollectionViewItemKey isEqual:]
// 118.0ms 1.7% 118.0 _objc_rootReleaseWasZero
// 105.0ms 1.5% 105.0 DYLD-STUB$$CFDictionarySetValue$shim
if (pickerCollectionView.contentOffset.y < 0.0f) {
[self appendPastDates];
}
if (pickerCollectionView.contentOffset.y > (pickerCollectionView.contentSize.height - CGRectGetHeight(pickerCollectionView.bounds))) {
[self appendFutureDates];
}
}
Здесь выше ключ, чтобы достичь внутреннего мира: -)
Как вы можете видеть, логика, говорящая в терминах y-компонента, т.е. высоты, если pickerCollectionView.contentOffset становится меньше нуля, мы будем продолжать добавлять прошлые даты на 6 месяцев, и если pickerCollectionView.contentOffset становится больше, то разница contentSize и bounds, мы будем добавлять будущие даты на 6 месяцев.
Но в моей жизни ничего не происходит, мой друг, Эти две функции - это все.
- (void)appendPastDates
{
[self shiftDatesByComponents:((^{
NSDateComponents *dateComponents = [NSDateComponents new];
dateComponents.month = -6;
return dateComponents;
})())];
}
- (void)appendFutureDates
{
[self shiftDatesByComponents:((^{
NSDateComponents *dateComponents = [NSDateComponents new];
dateComponents.month = 6;
return dateComponents;
})())];
}
В этих двух функциях вы заметите, что блок выполнен, его shiftDatesByComponents, его сердце логики по мне, потому что этот парень делает настоящую магию, ее немного сложнее, вот она:
- (void)shiftDatesByComponents:(NSDateComponents *)components
{
RSDFDatePickerCollectionView *cv = self.collectionView;
RSDFDatePickerCollectionViewLayout *cvLayout = (RSDFDatePickerCollectionViewLayout *)self.collectionView.collectionViewLayout;
NSArray *visibleCells = [cv visibleCells];
if (![visibleCells count])
return;
NSIndexPath *fromIndexPath = [cv indexPathForCell:((UICollectionViewCell *)visibleCells[0]) ];
NSInteger fromSection = fromIndexPath.section;
NSDate *fromSectionOfDate = [self dateForFirstDayInSection:fromSection];
UICollectionViewLayoutAttributes *fromAttrs = [cvLayout layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:fromSection]];
CGPoint fromSectionOrigin = [self convertPoint:fromAttrs.frame.origin fromView:cv];
_fromDate = [self pickerDateFromDate:[self.calendar dateByAddingComponents:components toDate:[self dateFromPickerDate:self.fromDate] options:0]];
_toDate = [self pickerDateFromDate:[self.calendar dateByAddingComponents:components toDate:[self dateFromPickerDate:self.toDate] options:0]];
#if 0
// This solution trips up the collection view a bit
// because our reload is reactionary, and happens before a relayout
// since we must do it to avoid flickering and to heckle the CA transaction (?)
// that could be a small red flag too
[cv performBatchUpdates:^{
if (components.month < 0) {
[cv deleteSections:[NSIndexSet indexSetWithIndexesInRange:(NSRange){
cv.numberOfSections - abs(components.month),
abs(components.month)
}]];
[cv insertSections:[NSIndexSet indexSetWithIndexesInRange:(NSRange){
0,
abs(components.month)
}]];
} else {
[cv insertSections:[NSIndexSet indexSetWithIndexesInRange:(NSRange){
cv.numberOfSections,
abs(components.month)
}]];
[cv deleteSections:[NSIndexSet indexSetWithIndexesInRange:(NSRange){
0,
abs(components.month)
}]];
}
} completion:^(BOOL finished) {
NSLog(@"%s %x", __PRETTY_FUNCTION__, finished);
}];
for (UIView *view in cv.subviews)
[view.layer removeAllAnimations];
#else
[cv reloadData];
[cvLayout invalidateLayout];
[cvLayout prepareLayout];
[self restoreSelection];
#endif
NSInteger toSection = [self sectionForDate:fromSectionOfDate];
UICollectionViewLayoutAttributes *toAttrs = [cvLayout layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:toSection]];
CGPoint toSectionOrigin = [self convertPoint:toAttrs.frame.origin fromView:cv];
[cv setContentOffset:(CGPoint) {
cv.contentOffset.x,
cv.contentOffset.y + (toSectionOrigin.y - fromSectionOrigin.y)
}];
}
Чтобы объяснить вышеприведенную функцию в нескольких строках, что она в основном делает, зависит от того, какой диапазон был рассчитан, будь то будущий 6-месячный ярость или 6-месячный диапазон, он манипулирует источником данных коллекции, будущие 6 месяцев не будут быть проблемой, вам просто нужно добавить материал, но последние 6 месяцев - это настоящая проблема.
Вот что происходит,
if (components.month < 0) {
[cv deleteSections:[NSIndexSet indexSetWithIndexesInRange:(NSRange){
cv.numberOfSections - abs(components.month),
abs(components.month)
}]];
[cv insertSections:[NSIndexSet indexSetWithIndexesInRange:(NSRange){
0,
abs(components.month)
}]];
}
Человек, я устал! Я не спал немного из-за этой проблемы, сделайте одно, если у вас есть какие-либо сомнения, пингуйте меня!
P.S. Это единственный способ, который дает вам гладкую прокрутку, как Официальное приложение Calendar iOS, я видел, как многие люди манипулировали scrollView и его методом делегатов для достижения бесконечной прокрутки, не видели никакой гладкости там. Дело в том, что манипулирование делегатом UICollectionView приведет к меньшему ущербу, если все сделано правильно, потому что они сделаны для тяжелой работы.