mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-09-06 20:54:04 +00:00
Update to latest state
This commit is contained in:
parent
1f55ef8945
commit
3e8ea64a1b
@ -8,17 +8,6 @@
|
|||||||
|
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
@class ASPagerNode;
|
|
||||||
|
|
||||||
@protocol ASPagerFlowLayoutPageProvider <NSObject>
|
|
||||||
|
|
||||||
/// Provides the current page index to the ASPagerFlowLayout
|
|
||||||
- (NSInteger)currentPageIndex;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface ASPagerFlowLayout : UICollectionViewFlowLayout
|
@interface ASPagerFlowLayout : UICollectionViewFlowLayout
|
||||||
|
|
||||||
- (instancetype)initWithPageProvider:(id<ASPagerFlowLayoutPageProvider>)pageProvider;
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -7,73 +7,78 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import "ASPagerFlowLayout.h"
|
#import "ASPagerFlowLayout.h"
|
||||||
#import "ASPagerNode.h"
|
|
||||||
|
|
||||||
@interface ASPagerFlowLayout ()
|
@interface ASPagerFlowLayout ()
|
||||||
|
|
||||||
@property (strong, nonatomic) NSIndexPath *currentIndexPath;
|
@property (strong, nonatomic) NSIndexPath *currentIndexPath;
|
||||||
@property (weak, nonatomic) id<ASPagerFlowLayoutPageProvider> pageProvider;
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation ASPagerFlowLayout
|
@implementation ASPagerFlowLayout {
|
||||||
|
BOOL _didRotate;
|
||||||
#pragma mark - Lifecycle
|
CGRect _cachedCollectionViewBounds;
|
||||||
|
|
||||||
- (instancetype)initWithPageProvider:(id<ASPagerFlowLayoutPageProvider>)pageProvider
|
|
||||||
{
|
|
||||||
self = [super init];
|
|
||||||
if (self == nil) { return self; }
|
|
||||||
_pageProvider = pageProvider;
|
|
||||||
return self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - UICollectionViewFlowLayout
|
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
|
||||||
|
|
||||||
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
|
|
||||||
{
|
{
|
||||||
CGRect oldBounds = self.collectionView.bounds;
|
NSInteger currentPage = ceil(proposedContentOffset.x / self.collectionView.bounds.size.width);
|
||||||
if (!CGSizeEqualToSize(oldBounds.size, newBounds.size)) {
|
self.currentIndexPath = [NSIndexPath indexPathForItem:currentPage inSection:0];
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NO;
|
return [super targetContentOffsetForProposedContentOffset:proposedContentOffset withScrollingVelocity:velocity];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
- (void)prepareForAnimatedBoundsChange:(CGRect)oldBounds
|
||||||
|
{
|
||||||
|
// Cache the current page if a rotation did happen. This happens before the rotation animation
|
||||||
|
// is occuring and the bounds changed so we use this as an opportunity to cache the current index path
|
||||||
|
if (_cachedCollectionViewBounds.size.width != self.collectionView.bounds.size.width) {
|
||||||
|
_cachedCollectionViewBounds = self.collectionView.bounds;
|
||||||
|
|
||||||
|
// Figurring out current page based on the old bounds visible space
|
||||||
|
CGRect visibleRect = oldBounds;
|
||||||
|
|
||||||
|
CGFloat visibleXCenter = CGRectGetMidX(visibleRect);
|
||||||
|
NSArray<UICollectionViewLayoutAttributes *> *layoutAttributes = [self layoutAttributesForElementsInRect:visibleRect];
|
||||||
|
for (UICollectionViewLayoutAttributes *attributes in layoutAttributes) {
|
||||||
|
if ([attributes representedElementCategory] == UICollectionElementCategoryCell && attributes.center.x == visibleXCenter) {
|
||||||
|
self.currentIndexPath = attributes.indexPath;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_didRotate = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
[super prepareForAnimatedBoundsChange:oldBounds];
|
||||||
|
}
|
||||||
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
|
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
|
||||||
|
{
|
||||||
|
// Don't mess around if the user is interacting with the page node. Although if just a rotation happened we should
|
||||||
|
// try to use the current index path to not end up setting the target content offset to something in between pages
|
||||||
|
if (_didRotate || (!self.collectionView.isDecelerating && !self.collectionView.isTracking)) {
|
||||||
|
_didRotate = NO;
|
||||||
|
if (self.currentIndexPath) {
|
||||||
|
return [self _targetContentOffsetForItemAtIndexPath:self.currentIndexPath proposedContentOffset:proposedContentOffset];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [super targetContentOffsetForProposedContentOffset:proposedContentOffset];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (CGPoint)_targetContentOffsetForItemAtIndexPath:(NSIndexPath *)indexPath proposedContentOffset:(CGPoint)proposedContentOffset
|
||||||
{
|
{
|
||||||
if ([self _dataSourceIsEmpty]) {
|
if ([self _dataSourceIsEmpty]) {
|
||||||
return proposedContentOffset;
|
return proposedContentOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_pageProvider == nil || [self _visibleRectIsInvalid]) {
|
CGFloat currentPageXOffset = self.currentIndexPath.item * CGRectGetWidth(self.collectionView.bounds);
|
||||||
return [super targetContentOffsetForProposedContentOffset:proposedContentOffset];
|
return CGPointMake(currentPageXOffset, proposedContentOffset.y);
|
||||||
}
|
|
||||||
|
|
||||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:_pageProvider.currentPageIndex inSection:0];
|
|
||||||
UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath];
|
|
||||||
CGFloat xOffset = (CGRectGetWidth(self.collectionView.bounds) - CGRectGetWidth(attributes.frame)) / 2;
|
|
||||||
return CGPointMake(attributes.frame.origin.x - xOffset, proposedContentOffset.y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Helper
|
|
||||||
|
|
||||||
- (BOOL)_dataSourceIsEmpty
|
- (BOOL)_dataSourceIsEmpty
|
||||||
{
|
{
|
||||||
return ([self.collectionView numberOfSections] == 0 || [self.collectionView numberOfItemsInSection:0] == 0);
|
return ([self.collectionView numberOfSections] == 0 || [self.collectionView numberOfItemsInSection:0] == 0);
|
||||||
}
|
|
||||||
|
|
||||||
- (CGRect)_visibleRect
|
|
||||||
{
|
|
||||||
CGRect visibleRect;
|
|
||||||
visibleRect.origin = self.collectionView.contentOffset;
|
|
||||||
visibleRect.size = self.collectionView.bounds.size;
|
|
||||||
return visibleRect;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)_visibleRectIsInvalid
|
|
||||||
{
|
|
||||||
return CGRectEqualToRect([self _visibleRect], CGRectZero);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -96,4 +96,3 @@
|
|||||||
- (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated;
|
- (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@ -12,26 +12,25 @@
|
|||||||
#import "ASPagerFlowLayout.h"
|
#import "ASPagerFlowLayout.h"
|
||||||
#import "UICollectionViewLayout+ASConvenience.h"
|
#import "UICollectionViewLayout+ASConvenience.h"
|
||||||
|
|
||||||
@interface ASPagerNode () <ASCollectionDataSource, ASCollectionViewDelegateFlowLayout, ASDelegateProxyInterceptor, ASPagerFlowLayoutPageProvider>
|
@interface ASPagerNode () <ASCollectionDataSource, ASCollectionViewDelegateFlowLayout, ASDelegateProxyInterceptor>
|
||||||
{
|
{
|
||||||
ASPagerFlowLayout *_flowLayout;
|
ASPagerFlowLayout *_flowLayout;
|
||||||
ASPagerNodeProxy *_dataSourceProxy;
|
ASPagerNodeProxy *_proxy;
|
||||||
ASPagerNodeProxy *_delegateProxy;
|
|
||||||
__weak id <ASPagerNodeDataSource> _pagerDataSource;
|
__weak id <ASPagerNodeDataSource> _pagerDataSource;
|
||||||
BOOL _pagerDataSourceImplementsNodeBlockAtIndex;
|
BOOL _pagerDataSourceImplementsNodeBlockAtIndex;
|
||||||
BOOL _pagerDataSourceImplementsConstrainedSizeForNode;
|
BOOL _pagerDataSourceImplementsConstrainedSizeForNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@property (nonatomic, assign, readonly) NSInteger numberOfPages;
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation ASPagerNode
|
@implementation ASPagerNode
|
||||||
@dynamic view, delegate, dataSource;
|
@dynamic view, delegate, dataSource;
|
||||||
|
|
||||||
|
#pragma mark - Lifecycle
|
||||||
|
|
||||||
- (instancetype)init
|
- (instancetype)init
|
||||||
{
|
{
|
||||||
ASPagerFlowLayout *flowLayout = [[ASPagerFlowLayout alloc] initWithPageProvider:self];
|
ASPagerFlowLayout *flowLayout = [[ASPagerFlowLayout alloc] init];
|
||||||
flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
||||||
flowLayout.minimumInteritemSpacing = 0;
|
flowLayout.minimumInteritemSpacing = 0;
|
||||||
flowLayout.minimumLineSpacing = 0;
|
flowLayout.minimumLineSpacing = 0;
|
||||||
@ -45,11 +44,12 @@
|
|||||||
self = [super initWithCollectionViewLayout:flowLayout];
|
self = [super initWithCollectionViewLayout:flowLayout];
|
||||||
if (self != nil) {
|
if (self != nil) {
|
||||||
_flowLayout = flowLayout;
|
_flowLayout = flowLayout;
|
||||||
_currentPageIndex = 0;
|
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma mark - ASDisplayNode
|
||||||
|
|
||||||
- (void)didLoad
|
- (void)didLoad
|
||||||
{
|
{
|
||||||
[super didLoad];
|
[super didLoad];
|
||||||
@ -67,10 +67,6 @@
|
|||||||
// our view is only horizontally scrollable. This causes UICollectionViewFlowLayout to log a warning.
|
// our view is only horizontally scrollable. This causes UICollectionViewFlowLayout to log a warning.
|
||||||
// From here we cannot disable this directly (UIViewController's automaticallyAdjustsScrollViewInsets).
|
// From here we cannot disable this directly (UIViewController's automaticallyAdjustsScrollViewInsets).
|
||||||
cv.zeroContentInsets = YES;
|
cv.zeroContentInsets = YES;
|
||||||
|
|
||||||
// Set the super delegate to the pager for now to inject the scroll delegate calls. If the API consumer
|
|
||||||
// set's the delegate on the ASPagerNode we add an ASPagerNodeProxy in between in setDelegate:
|
|
||||||
super.delegate = self;
|
|
||||||
|
|
||||||
ASRangeTuningParameters minimumRenderParams = { .leadingBufferScreenfuls = 0.0, .trailingBufferScreenfuls = 0.0 };
|
ASRangeTuningParameters minimumRenderParams = { .leadingBufferScreenfuls = 0.0, .trailingBufferScreenfuls = 0.0 };
|
||||||
ASRangeTuningParameters minimumPreloadParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 };
|
ASRangeTuningParameters minimumPreloadParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 };
|
||||||
@ -83,34 +79,23 @@
|
|||||||
[self setTuningParameters:fullPreloadParams forRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypeFetchData];
|
[self setTuningParameters:fullPreloadParams forRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypeFetchData];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Getter / Setter
|
#pragma mark - Getters / Setters
|
||||||
|
|
||||||
- (NSInteger)numberOfPages
|
- (NSInteger)currentPageIndex
|
||||||
{
|
{
|
||||||
return [_pagerDataSource numberOfPagesInPagerNode:self];
|
return (self.view.contentOffset.x / CGRectGetWidth(self.view.bounds));
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Helpers
|
#pragma mark - Helpers
|
||||||
|
|
||||||
- (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated
|
- (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated
|
||||||
{
|
{
|
||||||
// Prevent an exception to scroll to an index path that is invalid
|
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0];
|
||||||
if (index >= 0 && index < self.numberOfPages) {
|
[self.view scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionLeft animated:animated];
|
||||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0];
|
|
||||||
[self.view scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionLeft animated:animated];
|
|
||||||
|
|
||||||
_currentPageIndex = index;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - ASCollectionViewDataSource
|
#pragma mark - ASCollectionViewDataSource
|
||||||
|
|
||||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
|
|
||||||
{
|
|
||||||
ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load nodes to display");
|
|
||||||
return self.numberOfPages;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath
|
- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||||
{
|
{
|
||||||
ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load nodes to display");
|
ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load nodes to display");
|
||||||
@ -121,6 +106,12 @@
|
|||||||
return [_pagerDataSource pagerNode:self nodeBlockAtIndex:indexPath.item];
|
return [_pagerDataSource pagerNode:self nodeBlockAtIndex:indexPath.item];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load nodes to display");
|
||||||
|
return [_pagerDataSource numberOfPagesInPagerNode:self];
|
||||||
|
}
|
||||||
|
|
||||||
- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
|
- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
|
||||||
{
|
{
|
||||||
if (_pagerDataSourceImplementsConstrainedSizeForNode) {
|
if (_pagerDataSourceImplementsConstrainedSizeForNode) {
|
||||||
@ -129,7 +120,7 @@
|
|||||||
return ASSizeRangeMake(CGSizeZero, self.view.bounds.size);
|
return ASSizeRangeMake(CGSizeZero, self.view.bounds.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Proxies
|
#pragma mark - Data Source Proxy
|
||||||
|
|
||||||
- (id <ASPagerDataSource>)dataSource
|
- (id <ASPagerDataSource>)dataSource
|
||||||
{
|
{
|
||||||
@ -142,59 +133,20 @@
|
|||||||
_pagerDataSource = pagerDataSource;
|
_pagerDataSource = pagerDataSource;
|
||||||
|
|
||||||
_pagerDataSourceImplementsNodeBlockAtIndex = [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeBlockAtIndex:)];
|
_pagerDataSourceImplementsNodeBlockAtIndex = [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeBlockAtIndex:)];
|
||||||
_pagerDataSourceImplementsConstrainedSizeForNode = [_pagerDataSource respondsToSelector:@selector(pagerNode:constrainedSizeForNodeAtIndexPath:)];
|
|
||||||
|
|
||||||
// Data source must implement pagerNode:nodeBlockAtIndex: or pagerNode:nodeAtIndex:
|
// Data source must implement pagerNode:nodeBlockAtIndex: or pagerNode:nodeAtIndex:
|
||||||
ASDisplayNodeAssertTrue(_pagerDataSourceImplementsNodeBlockAtIndex || [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeAtIndex:)]);
|
ASDisplayNodeAssertTrue(_pagerDataSourceImplementsNodeBlockAtIndex || [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeAtIndex:)]);
|
||||||
|
|
||||||
_dataSourceProxy = pagerDataSource ? [[ASPagerNodeProxy alloc] initWithTarget:pagerDataSource interceptor:self] : nil;
|
_pagerDataSourceImplementsConstrainedSizeForNode = [_pagerDataSource respondsToSelector:@selector(pagerNode:constrainedSizeForNodeAtIndexPath:)];
|
||||||
|
|
||||||
super.dataSource = (id <ASCollectionDataSource>)_dataSourceProxy;
|
_proxy = pagerDataSource ? [[ASPagerNodeProxy alloc] initWithTarget:pagerDataSource interceptor:self] : nil;
|
||||||
|
|
||||||
|
super.dataSource = (id <ASCollectionDataSource>)_proxy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setDelegate:(id<ASCollectionDelegate>)delegate
|
|
||||||
{
|
|
||||||
_delegateProxy = delegate ? [[ASPagerNodeProxy alloc] initWithTarget:delegate interceptor:self] : nil;
|
|
||||||
|
|
||||||
super.delegate = (id <ASCollectionDelegate>)_delegateProxy;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy
|
- (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy
|
||||||
{
|
{
|
||||||
[self setDataSource:nil];
|
[self setDataSource:nil];
|
||||||
[self setDelegate:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - <ASCollectionDelegate>
|
|
||||||
|
|
||||||
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
|
|
||||||
{
|
|
||||||
CGFloat pageWidth = CGRectGetWidth(self.view.frame);
|
|
||||||
_currentPageIndex = floor((self.view.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)scrollViewWillEndDragging:(UIScrollView*)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint*)targetContentOffset
|
|
||||||
{
|
|
||||||
CGFloat pageWidth = CGRectGetWidth(self.view.frame);
|
|
||||||
NSInteger newPageIndex = _currentPageIndex;
|
|
||||||
|
|
||||||
if (velocity.x == 0) {
|
|
||||||
// Handle slow dragging not lifting finger
|
|
||||||
newPageIndex = floor((targetContentOffset->x - pageWidth / 2) / pageWidth) + 1;
|
|
||||||
} else {
|
|
||||||
newPageIndex = velocity.x > 0 ? _currentPageIndex + 1 : _currentPageIndex - 1;
|
|
||||||
|
|
||||||
if (newPageIndex < 0) {
|
|
||||||
newPageIndex = 0;
|
|
||||||
}
|
|
||||||
if (newPageIndex > self.view.contentSize.width / pageWidth) {
|
|
||||||
newPageIndex = ceil(self.view.contentSize.width / pageWidth) - 1.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_currentPageIndex = newPageIndex;
|
|
||||||
|
|
||||||
*targetContentOffset = CGPointMake(newPageIndex * pageWidth, targetContentOffset->y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user