UICollectionview滚动水平和垂直

时间:2015-03-27 14:13:58

标签: ios objective-c uiscrollview uicollectionview

我必须构建一个水平和垂直的UICollectionView可滚动,我知道网格布局只能沿着一个轴滚动,水平或垂直,所以我读了一些帖子,我尝试了不同的解决方案,但最简单的是放UIScrollView中的UICollectionview。通过这种方式,CollectionView可以垂直滚动,UIScrollView可以水平滚动。 问题是垂直滚动是困难的,不是流动的,并且经常停止,直到你再次点击并再次拖动。 你能建议一个解决方案吗?感谢

UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
UIScrollView *backgroundScroll = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
backgroundScroll.scrollEnabled = YES;     
[self.view addSubview:backgroundScroll];
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(10, 15, 1020, [UIScreen mainScreen].bounds.size.height - 35) collectionViewLayout:layout];
[backgroundScroll addSubview:_collectionView];
_collectionView.contentInset = UIEdgeInsetsMake(0, 0, 50, 0);         
_collectionView.scrollEnabled = YES;

我已经实施了这个方法:

- (void)viewDidLayoutSubviews {
    backgroundScroll.contentSize = self.collectionView.frame.size;
}

2 个答案:

答案 0 :(得分:2)

执行此操作的方法是创建自定义UICollectionViewLayout子类。

我最近不得不这样做。

让我去拿文件......一秒......

首先,您不能轻易地使用UICollectionViewFlowLayout的子类。流动布局设计为在一个方向上适合内容并在另一个方向上滚动。这不是你想要的。

虽然创建一个自定义布局来为您完成此任务并非常困难。

标题文件

@interface GridCollectionViewLayout : UICollectionViewLayout

// properties to configure the size and spacing of the grid
@property (nonatomic) CGSize itemSize;
@property (nonatomic) CGFloat itemSpacing;

// this method was used because I was switching between layouts    
- (void)configureCollectionViewForLayout:(UICollectionView *)collectionView;

@end

<强>实施

#import "GridCollectionViewLayout.h"

@interface GridCollectionViewLayout ()

@property (nonatomic, strong) NSDictionary *layoutInfo;

@end

@implementation GridCollectionViewLayout

为代码和界面构建器创建inits ...

- (id)init
{
    self = [super init];
    if (self) {
        [self setup];
    }

    return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];
    if (self) {
        [self setup];
    }

    return self;
}

安装程序默认属性值...

- (void)setup
{
    self.itemSize = CGSizeMake(50.0, 50.0);
    self.itemSpacing = 10.0;
}

这是因为我在不同的布局之间进行了更改,但它显示了设置布局所需的内容。

- (void)configureCollectionViewForLayout:(UICollectionView *)collectionView
{
    collectionView.alwaysBounceHorizontal = YES;

    [collectionView setCollectionViewLayout:self animated:NO];
}

必需的方法。这将迭代项目并为每个项目创建框架CGRect。将它们保存到字典中。

- (void)prepareLayout
{
    NSMutableDictionary *cellLayoutInfo = [NSMutableDictionary dictionary];

    NSInteger sectionCount = [self.collectionView numberOfSections];
    NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];

    for (NSInteger section = 0; section < sectionCount; section++) {
        NSInteger itemCount = [self.collectionView numberOfItemsInSection:section];

        for (NSInteger item = 0; item < itemCount; item++) {
            indexPath = [NSIndexPath indexPathForItem:item inSection:section];

            UICollectionViewLayoutAttributes *itemAttributes =
            [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
            itemAttributes.frame = [self frameForAssessmentAtIndexPath:indexPath];

            cellLayoutInfo[indexPath] = itemAttributes;
        }
    }

    self.layoutInfo = cellLayoutInfo;
}

这是一种在给定索引处快速获取帧的便捷方法。

- (CGRect)frameForIndexPath:(NSIndexPath *)indexPath
{
    NSInteger column = indexPath.section;
    NSInteger row = indexPath.item;

    CGFloat originX = column * (self.itemSize.width + self.itemSpacing);
    CGFloat originY = row * (self.itemSize.height + self.itemSpacing);

    return CGRectMake(originX, originY, self.itemSize.width, self.itemSize.height);
}

计算内容大小的必需方法。这只是将部分或项目的数量乘以大小和间距属性。这允许在两个方向上滚动,因为内容大小可以大于集合视图的宽度和高度。

- (CGSize)collectionViewContentSize
{
    NSInteger sectionCount = [self.collectionView numberOfSections];

    if (sectionCount == 0) {
        return CGSizeZero;
    }

    NSInteger itemCount = [self.collectionView numberOfItemsInSection:0];

    CGFloat width = (self.itemSize.width + self.itemSpacing) * sectionCount - self.itemSpacing;
    CGFloat height = (self.itemSize.height + self.itemSpacing) * itemCount - self.itemSpacing;

    return CGSizeMake(width, height);
}

必需的方法。这些告诉集合视图需要放置每个项目的位置。

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    return self.layoutInfo[indexPath];
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSMutableArray *allAttributes = [NSMutableArray array];

    [self.layoutInfo enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath, UICollectionViewLayoutAttributes *attributes, BOOL *stop) {
        if (CGRectIntersectsRect(attributes.frame, rect)) {
            [allAttributes addObject:attributes];
        }
    }];

    return allAttributes;
}

@end

当然,这种情况下的布局特定于我个人的问题。

通过使每个部分成为列并且每个部分中的项目是行来进行布局。所以像这样......

xy = item y in section x

00 10 20 30 ...
01 11 21 31 ...
02 12 22 32 ...
.  .  .  .
.  .  .  .
.  .  .  .

显然,部分中可以有无限数量的部分或项目,所以我必须在两个方向上滚动。

创建布局类后,只需将其设置为集合视图的布局即可。您可以在代码collectionView.collectionViewLayout = myLayout中执行此操作,也可以使用&#34; layout&#34;在Interface Builder中执行此操作。集合视图中的属性。

答案 1 :(得分:0)

我想介绍一种不同的方法来创建一个向一个方向滚动的UICollectionView,同时显示包含向相反方向滚动的CollectionView的单元格。通过实现此集合视图,在用于集合视图的UICollectionViewFlowLayout实例上设置scrollDirection,此解决方案提供了对用户交互的无缝响应。

解决方案是UICollectionView的子类,并添加了一个延迟手势识别器,它拦截用户的触摸,延迟它们一段时间以确定用户想要滚动的方向,然后在集合视图上取消panningRecognizer滚动那个特定的方向。

import Foundation
import UIKit

class UIDirectionAbidingCollectionView : UICollectionView {

    override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
        super.init(frame: frame, collectionViewLayout: layout)
        setupDelayRecognizer()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupDelayRecognizer()
    }

    func setupDelayRecognizer() {
        addGestureRecognizer(delayPanGestureRecognizer)

        // Delay the touches on the default recognizer on the collection view 

        panGestureRecognizer.delaysTouchesBegan = true
    }

    // This gesture recognizer controls the response to the user's touches
    // by cancelling by failing panGesture recognizer on the collection view
    // that scrolls in the opposite direction.

    lazy var delayPanGestureRecognizer: UIPanGestureRecognizer = {
        var recognizer = UIPanGestureRecognizer()
        recognizer.delegate = self
        return recognizer
    }()
}

extension UIDirectionAbidingCollectionView: UIGestureRecognizerDelegate {

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {

        // Ensure that the delay recognizer needs to fails for the 
        // default panning recognizers to receives the touches

        if (gestureRecognizer == delayPanGestureRecognizer &&
            otherGestureRecognizer == panGestureRecognizer) 
        {
            return true
        }

        return false
    }

    override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {

        // If the recognizer in question is our delay recognizer
        // lets check to see if it should begin receiving touches

        if gestureRecognizer == delayPanGestureRecognizer {

            // First retrieve the direction our flowlayout intends to scroll

            if let flowLayout = self.collectionViewLayout as? UICollectionViewFlowLayout {


                let scrollDirection = flowLayout.scrollDirection

                // Retrieve the translation of the delayPanningRecognizer 

                let translation = delayPanGestureRecognizer.translation(in: self)

                // Calculate the magnitude of change for the y and x axis 

                let xTransaltionValue = (translation.x * translation.x)
                let yTransaltionValue = (translation.y * translation.y)

                if scrollDirection == .vertical && xTransaltionValue > yTransaltionValue {

                   // If the scroll direction of the flowlayout is vertical,
                   // and the magnitude in the horizontal direction
                   // is greater than the horizontal, begin receiving touches.
                   // Since the delay recognizer doesn't fail, the vertical
                   // panning recognizer will fail to start on the collection view

                   return true
                }
                else if scrollDirection == .horizontal && xTransaltionValue < yTransaltionValue {

                    // If the scroll direction of the flowlayout is horizontal,
                    // and the magnitude in the vertical direction
                    // is greater than the horizontal, begin receiving touches.
                    // Since the delay recognizer doesn't fail, the horizontal
                    // panning recognizer will fail to start on the collection view

                    return true
                }
                else {

                    // Fail the delay recognizer, and allows the collection 
                    // view to continue as usual
                    return false
                }
            }
        }

        return true
    }
}