mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-09-01 18:33:10 +00:00
365 lines
16 KiB
Plaintext
365 lines
16 KiB
Plaintext
//
|
|
// ASIGListAdapterBasedDataSource.mm
|
|
// Texture
|
|
//
|
|
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
|
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
|
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
|
|
#import <AsyncDisplayKit/ASAvailability.h>
|
|
|
|
#if AS_IG_LIST_KIT
|
|
|
|
#import "ASIGListAdapterBasedDataSource.h"
|
|
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
|
#import <objc/runtime.h>
|
|
|
|
typedef IGListSectionController<ASSectionController> ASIGSectionController;
|
|
|
|
/// The optional methods that a class implements from ASSectionController.
|
|
/// Note: Bitfields are not supported by NSValue so we can't use them.
|
|
typedef struct {
|
|
BOOL sizeRangeForItem;
|
|
BOOL shouldBatchFetch;
|
|
BOOL beginBatchFetchWithContext;
|
|
} ASSectionControllerOverrides;
|
|
|
|
/// The optional methods that a class implements from ASSupplementaryNodeSource.
|
|
/// Note: Bitfields are not supported by NSValue so we can't use them.
|
|
typedef struct {
|
|
BOOL sizeRangeForSupplementary;
|
|
} ASSupplementarySourceOverrides;
|
|
|
|
@protocol ASIGSupplementaryNodeSource <IGListSupplementaryViewSource, ASSupplementaryNodeSource>
|
|
@end
|
|
|
|
@interface ASIGListAdapterBasedDataSource ()
|
|
@property (nonatomic, weak, readonly) IGListAdapter *listAdapter;
|
|
@property (nonatomic, readonly) id<UICollectionViewDelegateFlowLayout> delegate;
|
|
@property (nonatomic, readonly) id<UICollectionViewDataSource> dataSource;
|
|
@property (nonatomic, weak, readonly) id<ASCollectionDelegate> collectionDelegate;
|
|
|
|
/**
|
|
* The section controller that we will forward beginBatchFetchWithContext: to.
|
|
* Since shouldBatchFetch: is called on main, we capture the last section controller in there,
|
|
* and then we use it and clear it in beginBatchFetchWithContext: (on default queue).
|
|
*
|
|
* It is safe to use it without a lock in this limited way, since those two methods will
|
|
* never execute in parallel.
|
|
*/
|
|
@property (nonatomic, weak) ASIGSectionController *sectionControllerForBatchFetching;
|
|
@end
|
|
|
|
@implementation ASIGListAdapterBasedDataSource
|
|
|
|
- (instancetype)initWithListAdapter:(IGListAdapter *)listAdapter collectionDelegate:(nullable id<ASCollectionDelegate>)collectionDelegate
|
|
{
|
|
if (self = [super init]) {
|
|
#if IG_LIST_COLLECTION_VIEW
|
|
[ASIGListAdapterBasedDataSource setASCollectionViewSuperclass];
|
|
#endif
|
|
[ASIGListAdapterBasedDataSource configureUpdater:listAdapter.updater];
|
|
|
|
ASDisplayNodeAssert([listAdapter conformsToProtocol:@protocol(UICollectionViewDataSource)], @"Expected IGListAdapter to conform to UICollectionViewDataSource.");
|
|
ASDisplayNodeAssert([listAdapter conformsToProtocol:@protocol(UICollectionViewDelegateFlowLayout)], @"Expected IGListAdapter to conform to UICollectionViewDelegateFlowLayout.");
|
|
_listAdapter = listAdapter;
|
|
_collectionDelegate = collectionDelegate;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (id<UICollectionViewDataSource>)dataSource
|
|
{
|
|
return (id<UICollectionViewDataSource>)_listAdapter;
|
|
}
|
|
|
|
- (id<UICollectionViewDelegateFlowLayout>)delegate
|
|
{
|
|
return (id<UICollectionViewDelegateFlowLayout>)_listAdapter;
|
|
}
|
|
|
|
#pragma mark - ASCollectionDelegate
|
|
|
|
- (void)collectionNode:(ASCollectionNode *)collectionNode didSelectItemAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
[self.delegate collectionView:collectionNode.view didSelectItemAtIndexPath:indexPath];
|
|
}
|
|
|
|
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
|
{
|
|
[self.delegate scrollViewDidScroll:scrollView];
|
|
}
|
|
|
|
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
|
|
{
|
|
[self.delegate scrollViewWillBeginDragging:scrollView];
|
|
}
|
|
|
|
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
|
|
{
|
|
// IGListAdapter doesn't implement scrollViewWillEndDragging yet (pending pull request), so we need this check for now. Doesn't hurt to have it anyways :)
|
|
if ([self.delegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) {
|
|
[self.delegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
|
|
}
|
|
}
|
|
|
|
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
|
|
{
|
|
[self.delegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate];
|
|
}
|
|
|
|
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
|
|
{
|
|
[self.delegate scrollViewDidEndDecelerating:scrollView];
|
|
}
|
|
|
|
- (BOOL)shouldBatchFetchForCollectionNode:(ASCollectionNode *)collectionNode
|
|
{
|
|
if ([_collectionDelegate respondsToSelector:@selector(shouldBatchFetchForCollectionNode:)]) {
|
|
return [_collectionDelegate shouldBatchFetchForCollectionNode:collectionNode];
|
|
}
|
|
|
|
NSInteger sectionCount = [self numberOfSectionsInCollectionNode:collectionNode];
|
|
if (sectionCount == 0) {
|
|
return NO;
|
|
}
|
|
|
|
// If they implement shouldBatchFetch, call it. Otherwise, just say YES if they implement beginBatchFetch.
|
|
ASIGSectionController *ctrl = [self sectionControllerForSection:sectionCount - 1];
|
|
ASSectionControllerOverrides o = [ASIGListAdapterBasedDataSource overridesForSectionControllerClass:ctrl.class];
|
|
BOOL result = (o.shouldBatchFetch ? [ctrl shouldBatchFetch] : o.beginBatchFetchWithContext);
|
|
if (result) {
|
|
self.sectionControllerForBatchFetching = ctrl;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
- (void)collectionNode:(ASCollectionNode *)collectionNode willBeginBatchFetchWithContext:(ASBatchContext *)context
|
|
{
|
|
if ([_collectionDelegate respondsToSelector:@selector(collectionNode:willBeginBatchFetchWithContext:)]) {
|
|
[_collectionDelegate collectionNode:collectionNode willBeginBatchFetchWithContext:context];
|
|
return;
|
|
}
|
|
|
|
ASIGSectionController *ctrl = self.sectionControllerForBatchFetching;
|
|
self.sectionControllerForBatchFetching = nil;
|
|
[ctrl beginBatchFetchWithContext:context];
|
|
}
|
|
|
|
/**
|
|
* Note: It is not documented that ASCollectionNode will forward these UIKit delegate calls if they are implemented.
|
|
* It is not considered harmful to do so, and adding them to documentation will confuse most users, who should
|
|
* instead using the ASCollectionDelegate callbacks.
|
|
*/
|
|
#pragma mark - ASCollectionDelegateInterop
|
|
|
|
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
[self.delegate collectionView:collectionView willDisplayCell:cell forItemAtIndexPath:indexPath];
|
|
}
|
|
|
|
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
[self.delegate collectionView:collectionView didEndDisplayingCell:cell forItemAtIndexPath:indexPath];
|
|
}
|
|
|
|
#pragma mark - ASCollectionDelegateFlowLayout
|
|
|
|
- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode sizeRangeForHeaderInSection:(NSInteger)section
|
|
{
|
|
id<ASIGSupplementaryNodeSource> src = [self supplementaryElementSourceForSection:section];
|
|
if ([ASIGListAdapterBasedDataSource overridesForSupplementarySourceClass:[src class]].sizeRangeForSupplementary) {
|
|
return [src sizeRangeForSupplementaryElementOfKind:UICollectionElementKindSectionHeader atIndex:0];
|
|
} else {
|
|
return ASSizeRangeZero;
|
|
}
|
|
}
|
|
|
|
- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode sizeRangeForFooterInSection:(NSInteger)section
|
|
{
|
|
id<ASIGSupplementaryNodeSource> src = [self supplementaryElementSourceForSection:section];
|
|
if ([ASIGListAdapterBasedDataSource overridesForSupplementarySourceClass:[src class]].sizeRangeForSupplementary) {
|
|
return [src sizeRangeForSupplementaryElementOfKind:UICollectionElementKindSectionFooter atIndex:0];
|
|
} else {
|
|
return ASSizeRangeZero;
|
|
}
|
|
}
|
|
|
|
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
|
|
{
|
|
return [self.delegate collectionView:collectionView layout:collectionViewLayout insetForSectionAtIndex:section];
|
|
}
|
|
|
|
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
|
|
{
|
|
return [self.delegate collectionView:collectionView layout:collectionViewLayout minimumLineSpacingForSectionAtIndex:section];
|
|
}
|
|
|
|
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
|
|
{
|
|
return [self.delegate collectionView:collectionView layout:collectionViewLayout minimumInteritemSpacingForSectionAtIndex:section];
|
|
}
|
|
|
|
#pragma mark - ASCollectionDataSource
|
|
|
|
- (NSInteger)collectionNode:(ASCollectionNode *)collectionNode numberOfItemsInSection:(NSInteger)section
|
|
{
|
|
return [self.dataSource collectionView:collectionNode.view numberOfItemsInSection:section];
|
|
}
|
|
|
|
- (NSInteger)numberOfSectionsInCollectionNode:(ASCollectionNode *)collectionNode
|
|
{
|
|
return [self.dataSource numberOfSectionsInCollectionView:collectionNode.view];
|
|
}
|
|
|
|
- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
ASIGSectionController *ctrl = [self sectionControllerForSection:indexPath.section];
|
|
ASDisplayNodeAssert([ctrl respondsToSelector:@selector(nodeBlockForItemAtIndex:)], @"Expected section controller to respond to to %@. Controller: %@", NSStringFromSelector(@selector(nodeBlockForItemAtIndex:)), ctrl);
|
|
return [ctrl nodeBlockForItemAtIndex:indexPath.item];
|
|
}
|
|
|
|
- (ASCellNode *)collectionNode:(ASCollectionNode *)collectionNode nodeForItemAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
ASIGSectionController *ctrl = [self sectionControllerForSection:indexPath.section];
|
|
ASDisplayNodeAssert([ctrl respondsToSelector:@selector(nodeForItemAtIndex:)], @"Expected section controller to respond to to %@. Controller: %@", NSStringFromSelector(@selector(nodeForItemAtIndex:)), ctrl);
|
|
return [ctrl nodeForItemAtIndex:indexPath.item];
|
|
}
|
|
|
|
- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode constrainedSizeForItemAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
ASIGSectionController *ctrl = [self sectionControllerForSection:indexPath.section];
|
|
if ([ASIGListAdapterBasedDataSource overridesForSectionControllerClass:ctrl.class].sizeRangeForItem) {
|
|
return [ctrl sizeRangeForItemAtIndex:indexPath.item];
|
|
} else {
|
|
return ASSizeRangeUnconstrained;
|
|
}
|
|
}
|
|
|
|
- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
id<ASSupplementaryNodeSource> ctrl = [self supplementaryElementSourceForSection:indexPath.section];
|
|
ASDisplayNodeAssert([ctrl respondsToSelector:@selector(nodeBlockForSupplementaryElementOfKind:atIndex:)], @"Expected section controller to respond to to %@. Controller: %@", NSStringFromSelector(@selector(nodeBlockForSupplementaryElementOfKind:atIndex:)), ctrl);
|
|
return [ctrl nodeBlockForSupplementaryElementOfKind:kind atIndex:indexPath.item];
|
|
}
|
|
|
|
- (ASCellNode *)collectionNode:(ASCollectionNode *)collectionNode nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
id<ASSupplementaryNodeSource> ctrl = [self supplementaryElementSourceForSection:indexPath.section];
|
|
ASDisplayNodeAssert([ctrl respondsToSelector:@selector(nodeForSupplementaryElementOfKind:atIndex:)], @"Expected section controller to respond to to %@. Controller: %@", NSStringFromSelector(@selector(nodeForSupplementaryElementOfKind:atIndex:)), ctrl);
|
|
return [ctrl nodeForSupplementaryElementOfKind:kind atIndex:indexPath.item];
|
|
}
|
|
|
|
- (NSArray<NSString *> *)collectionNode:(ASCollectionNode *)collectionNode supplementaryElementKindsInSection:(NSInteger)section
|
|
{
|
|
return [[self supplementaryElementSourceForSection:section] supportedElementKinds];
|
|
}
|
|
|
|
#pragma mark - ASCollectionDataSourceInterop
|
|
|
|
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath
|
|
{
|
|
return [self.dataSource collectionView:collectionView cellForItemAtIndexPath:indexPath];
|
|
}
|
|
|
|
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
return [self.dataSource collectionView:collectionView viewForSupplementaryElementOfKind:kind atIndexPath:indexPath];
|
|
}
|
|
|
|
+ (BOOL)dequeuesCellsForNodeBackedItems
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
#pragma mark - Helpers
|
|
|
|
- (id<ASIGSupplementaryNodeSource>)supplementaryElementSourceForSection:(NSInteger)section
|
|
{
|
|
ASIGSectionController *ctrl = [self sectionControllerForSection:section];
|
|
id<ASIGSupplementaryNodeSource> src = (id<ASIGSupplementaryNodeSource>)ctrl.supplementaryViewSource;
|
|
ASDisplayNodeAssert(src == nil || [src conformsToProtocol:@protocol(ASSupplementaryNodeSource)], @"Supplementary view source should conform to %@", NSStringFromProtocol(@protocol(ASSupplementaryNodeSource)));
|
|
return src;
|
|
}
|
|
|
|
- (ASIGSectionController *)sectionControllerForSection:(NSInteger)section
|
|
{
|
|
id object = [_listAdapter objectAtSection:section];
|
|
ASIGSectionController *ctrl = (ASIGSectionController *)[_listAdapter sectionControllerForObject:object];
|
|
ASDisplayNodeAssert([ctrl conformsToProtocol:@protocol(ASSectionController)], @"Expected section controller to conform to %@. Controller: %@", NSStringFromProtocol(@protocol(ASSectionController)), ctrl);
|
|
return ctrl;
|
|
}
|
|
|
|
/// If needed, set ASCollectionView's superclass to IGListCollectionView (IGListKit < 3.0).
|
|
#if IG_LIST_COLLECTION_VIEW
|
|
+ (void)setASCollectionViewSuperclass
|
|
{
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
class_setSuperclass([ASCollectionView class], [IGListCollectionView class]);
|
|
});
|
|
#pragma clang diagnostic pop
|
|
}
|
|
#endif
|
|
|
|
/// Ensure updater won't call reloadData on us.
|
|
+ (void)configureUpdater:(id<IGListUpdatingDelegate>)updater
|
|
{
|
|
// Cast to NSObject will be removed after https://github.com/Instagram/IGListKit/pull/435
|
|
if ([(id<NSObject>)updater isKindOfClass:[IGListAdapterUpdater class]]) {
|
|
[(IGListAdapterUpdater *)updater setAllowsBackgroundReloading:NO];
|
|
} else {
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
NSLog(@"WARNING: Use of non-%@ updater with AsyncDisplayKit is discouraged. Updater: %@", NSStringFromClass([IGListAdapterUpdater class]), updater);
|
|
});
|
|
}
|
|
}
|
|
|
|
+ (ASSupplementarySourceOverrides)overridesForSupplementarySourceClass:(Class)c
|
|
{
|
|
static NSCache<Class, NSValue *> *cache;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
cache = [[NSCache alloc] init];
|
|
});
|
|
NSValue *obj = [cache objectForKey:c];
|
|
ASSupplementarySourceOverrides o;
|
|
if (obj == nil) {
|
|
o.sizeRangeForSupplementary = [c instancesRespondToSelector:@selector(sizeRangeForSupplementaryElementOfKind:atIndex:)];
|
|
obj = [NSValue valueWithBytes:&o objCType:@encode(ASSupplementarySourceOverrides)];
|
|
[cache setObject:obj forKey:c];
|
|
} else {
|
|
[obj getValue:&o];
|
|
}
|
|
return o;
|
|
}
|
|
|
|
+ (ASSectionControllerOverrides)overridesForSectionControllerClass:(Class)c
|
|
{
|
|
static NSCache<Class, NSValue *> *cache;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
cache = [[NSCache alloc] init];
|
|
});
|
|
NSValue *obj = [cache objectForKey:c];
|
|
ASSectionControllerOverrides o;
|
|
if (obj == nil) {
|
|
o.sizeRangeForItem = [c instancesRespondToSelector:@selector(sizeRangeForItemAtIndex:)];
|
|
o.beginBatchFetchWithContext = [c instancesRespondToSelector:@selector(beginBatchFetchWithContext:)];
|
|
o.shouldBatchFetch = [c instancesRespondToSelector:@selector(shouldBatchFetch)];
|
|
obj = [NSValue valueWithBytes:&o objCType:@encode(ASSectionControllerOverrides)];
|
|
[cache setObject:obj forKey:c];
|
|
} else {
|
|
[obj getValue:&o];
|
|
}
|
|
return o;
|
|
}
|
|
|
|
@end
|
|
|
|
#endif // AS_IG_LIST_KIT
|