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

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

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

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

有什么帮助吗?

【问题讨论】:

    标签: iphone ios objective-c ios6 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 来更新秘密滚动视图的内容大小。 stackoverflow.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 选项卡或应用商店搜索。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-10-17
        • 1970-01-01
        • 1970-01-01
        • 2017-07-03
        • 2013-10-15
        • 2016-10-01
        • 2013-11-05
        相关资源
        最近更新 更多