UICollectionView:像 Safari 选项卡或 App Store 搜索一样分页

Posted

技术标签:

【中文标题】UICollectionView:像 Safari 选项卡或 App Store 搜索一样分页【英文标题】:UICollectionView: paging like Safari tabs or App Store search 【发布时间】:2013-04-10 07:22:58 【问题描述】:

我想在我的应用中实现“卡片”,例如 Safari 选项卡或 App Store 搜索。

我将在屏幕中心向用户显示一张卡片,并在左右两侧显示上一张和下一张卡片的一部分。 (例如,请参阅 App Store 搜索或 Safari 选项卡)

我决定使用UICollectionView,我需要改变页面大小(没找到怎么做)或者实现自己的布局子类(不知道怎么做)?

有什么帮助吗?

【问题讨论】:

【参考方案1】:

以下是我找到的获得此效果的最简单方法。它涉及您的收藏视图和一个额外的秘密滚动视图。

设置您的收藏视图

设置您的集合视图及其所有数据源方法。 框出集合视图;它应该跨越您希望可见的整个宽度。

设置集合视图的contentInset:

_collectionView.contentInset = UIEdgeInsetsMake(0, (self.view.frame.size.width-pageSize)/2, 0, (self.view.frame.size.width-pageSize)/2);

这有助于正确偏移单元格。

设置您的秘密滚动视图

创建一个滚动视图,将其放置在您喜欢的任何位置。如果您愿意,可以将其设置为hidden。 将滚动视图的边界大小设置为页面所需的大小。 将自己设置为滚动视图的代表。 将其contentSize 设置为集合视图的预期内容大小。

移动你的手势识别器

将秘密滚动视图的手势识别器添加到集合视图中,并禁用集合视图的手势识别器:

[_collectionView addGestureRecognizer:_secretScrollView.panGestureRecognizer];
_collectionView.panGestureRecognizer.enabled = NO;

委托

- (void) scrollViewDidScroll:(UIScrollView *)scrollView 
    CGPoint contentOffset = scrollView.contentOffset;
    contentOffset.x = contentOffset.x - _collectionView.contentInset.left;
    _collectionView.contentOffset = contentOffset;

随着滚动视图的移动,获取它的偏移量并将其设置为集合视图的偏移量。

我在这里写了一篇博客,所以请查看此链接以获取更新:http://khanlou.com/2013/04/paging-a-overflowing-collection-view/

【讨论】:

然而,这并没有将选择和点击事件正确地传递到 ios7 的底层集合视图。有什么想法吗? 在 iOS7 上(至少——在此之前可能是真的)你不需要滚动视图。只需检测左右滑动手势并使用它们来设置集合视图 1 页向前/向后的偏移量。 我一辈子都无法让它在 iOS7 上运行。 scrollViewDidScroll 的“秘密”滚动视图永远不会交付。 确保您的视图控制器符合 UIScrollView 委托协议并将其设置为“秘密”滚动视图的委托。 最后。我重新审视了这个解决方案,结果发现问题出在动态内容上。集合视图contentSizereloadData 之后没有正确更新,因此当视图最初加载时宽度始终为 0。作为一种解决方法(我还不完全理解),我使用_collectionView.collectionViewLayout.collectionViewContentSize 而不是_collectionView.contentSize 来更新秘密滚动视图的内容大小。 ***.com/q/19103333/23643【参考方案2】:

您可以继承 UICollectionViewFlowLayout 并像这样覆盖:

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)contentOffset 
                                 withScrollingVelocity:(CGPoint)velocity

    NSArray* layoutAttributesArray = 
   [self layoutAttributesForElementsInRect:self.collectionView.bounds];

    if(layoutAttributesArray.count == 0)
        return contentOffset;


    UICollectionViewLayoutAttributes* candidate = 
    layoutAttributesArray.firstObject;

    for (UICollectionViewLayoutAttributes* layoutAttributes in layoutAttributesArray)
    
        if (layoutAttributes.representedElementCategory != UICollectionElementCategoryCell)
            continue;


        if((velocity.x > 0.0 && layoutAttributes.center.x > candidate.center.x) ||
           (velocity.x <= 0.0 && layoutAttributes.center.x < candidate.center.x))
            candidate = layoutAttributes;


    

    return CGPointMake(candidate.center.x - self.collectionView.bounds.size.width * 0.5f, contentOffset.y);

这将根据速度获得下一个或上一个单元格......但是它不会捕捉到当前单元格。

【讨论】:

通过此委托方法为偏移返回不同的CGPoint 效果很好。停止在单元格上的过渡感觉非常顺利。 此外,在转换时将滚动视图设置为 snap 的技巧似乎是将集合视图的 decelerationRate 设置为 UIScrollViewDecelerationRateFast。为了更快地捕捉,如果您将 decelerationRate 设置为小于 0.990000 的值(这似乎是当前的 UIScrollViewDecelerationRateFast),它似乎也有点工作。【参考方案3】:

@Mike M's answer 在 Swift 中……

class CenteringFlowLayout: UICollectionViewFlowLayout 

    override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint 

        guard let collectionView = collectionView,
            let layoutAttributesArray = layoutAttributesForElementsInRect(collectionView.bounds),
            var candidate = layoutAttributesArray.first else  return proposedContentOffset 

        layoutAttributesArray.filter($0.representedElementCategory == .Cell ).forEach  layoutAttributes in

            if (velocity.x > 0 && layoutAttributes.center.x > candidate.center.x) ||
                (velocity.x <= 0 && layoutAttributes.center.x < candidate.center.x) 
                candidate = layoutAttributes
            
        

        return CGPoint(x: candidate.center.x - collectionView.bounds.width / 2, y: proposedContentOffset.y)
    


【讨论】:

【参考方案4】:

对 Soroush 的回答稍作修改,这对我有用。 我所做的唯一编辑而不是禁用手势:

[_collectionView addGestureRecognizer:_secretScrollView.panGestureRecognizer];
_collectionView.panGestureRecognizer.enabled = NO;

我在 collectionview 上禁用了滚动:

_collectionView.scrollEnabled = NO;

由于禁用手势也禁用了秘密滚动视图手势。

【讨论】:

【参考方案5】:

我将添加另一个解决方案。就地捕捉并不完美(不如设置启用分页时那么好,但效果很好)。

我已尝试实施 Soroush 的解决方案,但它对我不起作用。

因为UICollectionViewUIScrollView 的子类,它会响应一个重要的UIScrollViewDelegate 方法,即:

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
                     withVelocity:(CGPoint)velocity
              targetContentOffset:(inout CGPoint *)targetContentOffset

targetContentOffsetinout 指针)可让您重新定义集合视图的停止点(如果您水平滑动,则在本例中为 x)。

关于以下几个变量的简要说明:

self.cellWidth – 这是您的集合视图单元格的宽度(如果需要,您甚至可以在那里硬编码)

self.minimumLineSpacing - 这是您在单元格之间设置的最小行距

self.scrollingObjects 是集合视图中包含的对象数组(我需要这个主要用于计数,以了解何时停止滚动)

因此,想法是在集合视图的委托中实现此方法,如下所示:

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
                     withVelocity:(CGPoint)velocity
              targetContentOffset:(inout CGPoint *)targetContentOffset
       
    if (self.currentIndex == 0 && velocity.x < 0) 
        // we have reached the first user and we're trying to go back
        return;
    

    if (self.currentIndex == (self.scrollingObjects.count - 1) && velocity.x > 0) 
        // we have reached the last user and we're trying to go forward
        return;
    

    if (velocity.x < 0) 
        // swiping from left to right (going left; going back)
        self.currentIndex--;
     else 
        // swiping from right to left (going right; going forward)
        self.currentIndex++;
    

    float xPositionToStop = 0;

    if (self.currentIndex == 0) 
        // first row
        xPositionToStop = 0;
     else 
        // one of the next ones
        xPositionToStop = self.currentIndex * self.cellWidth + (self.currentIndex + 1) * self.minimumLineSpacing - ((scrollView.bounds.size.width - 2*self.minimumLineSpacing - self.cellWidth)/2);
    

    targetContentOffset->x = xPositionToStop;

    NSLog(@"Point of stopping: %@", NSStringFromCGPoint(*targetContentOffset));

期待您提出的任何反馈,这些反馈可以让卡扣效果更好。我也会继续寻找更好的解决方案...

【讨论】:

这似乎可行...但不能精确复制“启用分页”,感觉有点尴尬。【参考方案6】:

前面比较复杂,UICollectionView 是 UIScrollView 的子类,所以这样做:

[self.collectionView setPagingEnabled:YES];

你们都准备好了。

看到这个detailed tutorial。

【讨论】:

这不能解决分页时左右显示内容的问题,例如 safari 选项卡或应用商店搜索。

以上是关于UICollectionView:像 Safari 选项卡或 App Store 搜索一样分页的主要内容,如果未能解决你的问题,请参考以下文章

tvOS:当它像这样实现时无法滚动 UICollectionView 项目:UITableView 内的 UICollectionView

UICollectionView 像 iOS 主屏幕 [关闭]

UICollectionView 布局像 SnapChat?

UICollectionView 显示空单元格

如何水平居中 UICollectionView 单元格?

UICollectionView 具体解说学习