[英]Scrolling issue with a uitableview inside a uicollectionviewcell
[英]How can I synchronise the scrolling between a UITableView inside of a UICollectionViewCell (Inside a UICollectionViewController)
目前,我正在尝试同步嵌套在 UICollectionViewCell 内的 UITableView 之间的滚动
我的视图层次结构类似于:
我想要实现的是一种类似视图寻呼机的效果,您可以在页面之间滑动,当您垂直滚动时,视图的其余部分。即导航栏会优雅地响应它。
我现在正在做的是获取UITableView
的contentOffset.y
并相应地更新UICollectionView
内容偏移量。
我正在使用 NSNotificationCenter 在滚动 tableview 时通知父 ViewController。
@objc private func didScroll(_ notification: NSNotification) {
guard let userInfo = notification.userInfo as? [String: CGPoint] else { return }
guard let offset = userInfo["offset"] else { return }
let y = offset.y
collectionView?.setContentOffset(CGPoint(x: 0, y: y), animated: false)
}
结果如下所示:
主要问题是:
您无法向左或向右滑动,因为内容偏移量 x 是静态的并设置为 0
视图有点卷起来,看起来很糟糕
在我尝试之前,我的观点最终看起来像这样:
在这里你可以看到我描述的问题,视图没有响应 UITableView 滚动。
所以为了缩短它,我需要:
了解如何使 UICollectionView 与 UICollectionViewCell 内的 UITableView 同步滚动(并允许更改内容 offset.x)
我没有使用嵌套 UICollectionView/UITableView 的方法,认为很难解决两个 UIScrollView 之间的滚动同步问题。 这是另一种方法:保留一个 ListView(UICollectionView 或 UITableView)并添加滑动手势以实现水平滚动以与另一个显示部分列表数据的 ListView 进行分页。 分页后,刷新主 ListView 以显示新的类别数据。
// CollectionViewPagingLayout.h
@interface UICollectionViewPagingLayout : UICollectionViewFlowLayout
@property (nonatomic, assign) NSInteger pagingSection;
@property (nonatomic, assign) CGPoint pagingOffset;
@end
// CollectionViewPagingLayout.m
// The new InvalidationContext contains new flag, invalidatedOffset, to indicates that it is just offset change and doesn't need full layout
@interface UICollectionViewPagingLayoutInvalidationContext : UICollectionViewFlowLayoutInvalidationContext
@property (nonatomic, assign) BOOL invalidateOffset; // Paging Or Sticky
@end
@interface UICollectionViewPagingLayout()
{
BOOL m_layoutInvalidated; // Should be initialized to NO
}
@end
@implementation UICollectionViewPagingLayoutInvalidationContext
@end
@implementation UICollectionViewPagingLayout
@synthesize pagingOffset = m_pagingOffset;
@synthesize pagingSection = m_pagingSection;
// Skip initialization here...
+ (Class)invalidationContextClass
{
return [UICollectionViewPagingLayoutInvalidationContext class];
}
- (void)setPagingOffset:(CGPoint)pagingOffset
{
m_pagingOffset = pagingOffset;
[self invalidateOffset];
}
- (void)setPagingSection:(NSInteger)pagingSection
{
m_pagingSection = pagingSection;
[self invalidateOffset];
}
- (void)invalidateOffset
{
UICollectionViewPagingLayoutInvalidationContext *context = (UICollectionViewPagingLayoutInvalidationContext *)[[[UICollectionViewPagingLayout invalidationContextClass] alloc] init];
context.invalidateOffset = YES;
[self invalidateLayoutWithContext:context];
}
- (void)prepareLayout
{
if (m_layoutInvalidated)
{
[super prepareLayout];
m_layoutInvalidated = NO;
}
}
- (void)invalidateLayout
{
m_layoutInvalidated = YES;
[super invalidateLayout];
}
- (void)invalidateLayoutWithContext:(UICollectionViewLayoutInvalidationContext *)context
{
if ([context isKindOfClass:[UICollectionViewPagingLayout invalidationContextClass]])
{
UICollectionViewPagingLayoutInvalidationContext *pagingInvalidationContext = (UICollectionViewPagingLayoutInvalidationContext *)context;
if (!pagingInvalidationContext.invalidateOffset)
{
// It is not caused by internal offset change, should call prepareLayout
m_layoutInvalidated = YES;
}
}
else
{
// It is not caused by offset change, should call prepareLayout
m_layoutInvalidated = YES;
}
[super invalidateLayoutWithContext:context];
}
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray<UICollectionViewLayoutAttributes *> *layoutAttributesArray = [super layoutAttributesForElementsInRect:rect];
// PagingOffset
if (m_pagingSection != NSNotFound && !CGPointEqualToPoint(m_pagingOffset, CGPointZero))
{
for (UICollectionViewLayoutAttributes *layoutAttributes in layoutAttributesArray)
{
if (layoutAttributes.indexPath.section >= m_pagingSection)
{
layoutAttributes.frame = CGRectOffset(layoutAttributes.frame, m_pagingOffset.x, m_pagingOffset.y);
}
}
}
return layoutAttributesArray;
}
// PagingCollectionView.h
// Define a protocol so that subclass can provide necessary information, such as current page, page size, the views for next page,
@protocol UIPagingCollectionViewDelegate<NSObject>
@optional
- (BOOL)collectionView:(nonnull UICollectionView *)collectionView pagingShouldBeginAtLocation:(CGPoint)location withTranslation:(CGPoint)translation andVelocity:(CGPoint)velocity onSection:(out NSInteger *_Nullable)section;
- (NSInteger)pageForSection:(NSInteger)section inPagingCollectionView:(nonnull UIPagingCollectionView *)pagingCollectionView;
- (NSInteger)pageSizeForSection:(NSInteger)section inPagingCollectionView:(nonnull UIPagingCollectionView *)pagingCollectionView;
- (nullable __kindof UIView *)pagingCollectionView:(nonnull UIPagingCollectionView *)pagingCollectionView viewForPage:(NSInteger)page;
- (void)collectionView:(nonnull UIPagingCollectionView *)pagingCollectionView pagingWithOffset:(CGPoint) offset decelerating:(BOOL)decelerating;
/ Notify subclass the paging action is completed, subclass should check if page is changed with necessary actions, for example, switch page on main collectionview, remove the collections on the left/right side, etc..
- (void)collectionView:(nonnull UIPagingCollectionView *)pagingCollectionView pagingEnded toNewPage:(NSInteger)page;
@end
@interface UIPagingCollectionView : UICollectionView
@property (nonatomic, weak, nullable) id <UIPagingCollectionViewDelegate> pagingDelegate;
- (void)enablePagingWithDirection:(UICollectionViewScrollDirection)direction;
@end
// PagingCollectionView.m
@interface UIPagingCollectionView() <UIGestureRecognizerDelegate>
{
UIPanGestureRecognizer *m_swipeGuestureRecognizer;
UICollectionViewScrollDirection m_swipeDirection; // Should provide a public property to update for subclass
// subclass should has a strong reference and destroy it once pagination is completed and main collection finishes to load the data of new page
__weak UIView *m_leftView;
__weak UIView *m_rightView;
CGRect m_originalLeftFrame;
CGRect m_originalRightFrame;
}
@end
@implementation UIPagingCollectionView
@synthesize pagingDelegate = m_pagingDelegate;
- (void)enablePagingWithDirection:(UICollectionViewScrollDirection)direction
{
if (nil == m_swipeGuestureRecognizer)
{
m_swipeGuestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipe:)];
m_swipeGuestureRecognizer.delegate = self;
[self addGestureRecognizer:m_swipeGuestureRecognizer];
}
m_swipeDirection = direction;
}
- (void)offsetPagingViews:(CGPoint)offset
{
if (nil != m_leftView)
{
m_leftView.frame = CGRectOffset(m_originalLeftFrame, offset.x, offset.y);
}
if (nil != m_rightView)
{
m_rightView.frame = CGRectOffset(m_originalRightFrame, offset.x, offset.y);
}
}
- (void)handleSwipe:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer == m_swipeGuestureRecognizer)
{
switch(m_swipeGuestureRecognizer.state)
{
case UIGestureRecognizerStateBegan:
{
CGPoint location =[m_swipeGuestureRecognizer locationInView:self];
CGPoint translation =[m_swipeGuestureRecognizer translationInView:self];
CGPoint velocity =[m_swipeGuestureRecognizer velocityInView:self];
BOOL shouldBegin = NO;
NSInteger section = NSNotFound;
if ([self.pagingDelegate collectionView:self pagingShouldBeginAtLocation:location withTranslation:translation andVelocity:velocity onSection:§ion])
{
shouldBegin = YES;
}
if (shouldBegin)
{
NSInteger page = [m_pagingDelegate pageForSection:section inPagingCollectionView:self];
NSInteger pageSize = [m_pagingDelegate pageSizeForSection:section inPagingCollectionView:self];
// m_swipingContext.leftOrRight = (translation.x < 0);
BOOL leftOrRight = ((m_swipeDirection == UICollectionViewScrollDirectionHorizontal) && (velocity.x < 0)) || ((m_swipeDirection == UICollectionViewScrollDirectionVertical) && (velocity.y < 0));
// Check validation of newPage...
[self buildViewForNewPage:leftOrRight ? (page + 1) : (page - 1) withCurrentPage:page];
CGPoint offset = (m_swipeDirection == UICollectionViewScrollDirectionHorizontal) ? CGPointMake(translation.x, 0) : CGPointMake(0, translation.y);
[self pagingWithOffset:offset decelerating:NO andBindingPagingViews:YES];
// Add a flag to tell UIGestureRecognizerStateChanged and UIGestureRecognizerStateEnded that paging gesture is valid.
}
}
break;
case UIGestureRecognizerStateChanged:
{
if (/*the flag of paging gesture is valid*/)
{
CGPoint translation = [m_swipeGuestureRecognizer translationInView:self];
if (((m_swipeDirection == UICollectionViewScrollDirectionHorizontal) && (translation.x == 0)) || ((m_swipeDirection == UICollectionViewScrollDirectionVertical) && (translation.y == 0)))
{
// NO Movement
}
else
{
BOOL leftOrRight = ((m_swipeDirection == UICollectionViewScrollDirectionHorizontal) && (translation.x < 0)) || ((m_swipeDirection == UICollectionViewScrollDirectionVertical) && (translation.y < 0));
[self buildViewForNewPage:leftOrRight ? (page + 1) : (page - 1) withCurrentPage:page];
}
CGPoint offset = (m_swipeDirection == UICollectionViewScrollDirectionHorizontal) ? CGPointMake(translation.x, 0) : CGPointMake(0, translation.y);
[self pagingWithOffset:offset decelerating:NO andBindingPagingViews:YES];
}
}
break;
case UIGestureRecognizerStateEnded:
{
if (nil != m_pagingContext)
{
CGPoint translation = [m_swipeGuestureRecognizer translationInView:self];
CGPoint offset = (m_swipeDirection == UICollectionViewScrollDirectionHorizontal) ? CGPointMake(translation.x, 0) : CGPointMake(0, translation.y);
[self pagingWithOffset:offset decelerating:NO andBindingPagingViews:YES];
if (((m_swipeDirection == UICollectionViewScrollDirectionHorizontal) && (translation.x == 0)) || ((m_swipeDirection == UICollectionViewScrollDirectionVertical) && (translation.y == 0)))
{
// NO Movement, everything is as same as old, just notify subclass
[m_pagingDelegate collectionView:self pagingEnded toNewPage:page];
break;
}
BOOL leftOrRight = ((m_swipeDirection == UICollectionViewScrollDirectionHorizontal) && (translation.x < 0)) || ((m_swipeDirection == UICollectionViewScrollDirectionVertical) && (translation.y < 0));
// Check (translation + sliding distance) is greater than half of size, if it is, start a sliding animation to complete the pagination and show new page
[m_pagingDelegate collectionView:self pagingEnded toNewPage:newPage];
// Otherwise, also start a sliding animation to move the current page to original position
[m_pagingDelegate collectionView:self pagingEnded toNewPage:page];
}
}
break;
default:
break;
}
}
}
- (void)pagingWithOffset:(CGPoint)offset decelerating:(BOOL)decelerating andBindingPagingViews:(BOOL)bindingPagingViews
{
if ([m_pagingDelegate conformsToProtocol:@protocol(UIPagingCollectionViewDelegate)] && [m_pagingDelegate respondsToSelector:@selector(collectionView:pagingWithOffset:decelerating:)])
{
[self.pagingDelegate collectionView:self pagingWithOffset:offset decelerating:decelerating];
}
if (bindingPagingViews)
{
[UIView performWithoutAnimation:^{
if (nil != self->m_leftView)
{
self->m_leftView.frame = CGRectOffset(self->m_originalLeftFrame, offset.x, offset.y);
}
if (nil != self->m_rightView)
{
self->m_rightView.frame = CGRectOffset(self->m_originalRightFrame, offset.x, offset.y);
}
}];
}
}
- (void)buildViewForNewPage:(UIPagingContext *)newPage withCurrentPage:(NSInteger)page
{
// newPage < page: draging dreiction is right
// newPage > page: draging dreiction is left
BOOL leftOrRight = newPage > page;
UIView * __strong *pView = leftOrRight ? (&m_leftView) : (&m_rightView);
if (nil != *pView)
{
return;
}
CGRect * pOriginalFrame = leftOrRight ? (&m_originalLeftFrame) : (&m_originalRightFrame);
UIView *view = [self.pagingDelegate pagingCollectionView:self viewForPage:newPage];
CGFloat xOffset = (m_swipeDirection == UICollectionViewScrollDirectionHorizontal) ? (pageContext.leftOrRight ? self.bounds.size.width : -self.bounds.size.width) : 0;
CGFloat yOffset = (m_swipeDirection == UICollectionViewScrollDirectionHorizontal) ? 0 : (pageContext.leftOrRight ? self.bounds.size.height : -self.bounds.size.height);
view.frame = CGRectOffset(view.frame, xOffset, yOffset);
*pOriginalFrame = view.frame;
*pView = view;
[self addSubview:view];
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer == m_swipeGuestureRecognizer)
{
CGPoint location =[m_swipeGuestureRecognizer locationInView:self];
CGPoint velocity =[m_swipeGuestureRecognizer velocityInView:self];
CGPoint translation =[m_swipeGuestureRecognizer translationInView:self];
BOOL shouldBegin = NO;
NSInteger section = NSNotFound;
// Check direction first
if (((m_swipeDirection == UICollectionViewScrollDirectionHorizontal) && (fabs(velocity.x) > fabs(velocity.y))) || ((m_swipeDirection == UICollectionViewScrollDirectionVertical) && (fabs(velocity.y) > fabs(velocity.x))))
{
if ([self.pagingDelegate collectionView:self pagingShouldBeginAtLocation:location withTranslation:translation andVelocity:velocity onSection:§ion])
{
shouldBegin = YES;
}
}
if (shouldBegin)
{
NSInteger page = [m_pagingDelegate pageForSection:section inPagingCollectionView:self];
NSInteger pageSize = [m_pagingDelegate pageSizeForSection:section inPagingCollectionView:self];
BOOL leftOrRight = ((m_swipeDirection == UICollectionViewScrollDirectionHorizontal) && (velocity.x < 0)) || ((m_swipeDirection == UICollectionViewScrollDirectionVertical) && (velocity.y < 0));
[self buildViewForNewPage:leftOrRight ? (page + 1) : (page - 1) withCurrentPage:page];
}
return shouldBegin;
}
return [super gestureRecognizerShouldBegin:gestureRecognizer];
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(nonnull UIGestureRecognizer *)otherGestureRecognizer
{
if ((gestureRecognizer == m_swipeGuestureRecognizer) &&
(otherGestureRecognizer == self.panGestureRecognizer))
{
return YES;
}
return NO;
}
@end
然后我们可以绕过嵌套 UIScrollViews 之间的滚动问题。 完整的示例代码在这里
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.