用连续卷轴查看;水平和垂直

时间:2013-03-21 13:42:32

标签: ios uiscrollview uicollectionview infinite-scroll

我一直在努力完成这项任务已有一段时间了。我想要开发的是一个scrollview或collectionview,它可以连续滚动垂直和水平。

这是我认为这应该是什么样子的图像。透明框是从存储器重新加载的视图/单元。一旦视图/单元格离开屏幕,它就应该被重新用于即将到来的新单元格......就像UITableViewController的工作方式一样。

Continuous scroll

我知道UICollectionView只能进行无限滚动水平或垂直,而不是两者。但是,我不知道如何使用UIScrollView执行此操作。 我尝试了代码attached to an answer on this question,我可以让它重新创建视图(例如%20),但这不是我真正需要的东西..此外,它不是连续的。

我知道这是可能的,因为HBO Go应用就是这样做的。我想要完全相同的功能。

我的问题:我如何实现目标?是否有任何指南/教程可以告诉我如何?我找不到任何。

4 个答案:

答案 0 :(得分:30)

通过使用距离中心一定距离后重新定位UIScrollView的技术,您可以获得无限滚动。首先,你需要让contentSize足够大,你可以滚动一下,所以我返回我的部分中项目数量的4倍和部分数量的4倍,并使用{{1}中的mod运算符1}}获取正确索引到我的数组的方法。然后,您必须覆盖cellForItemAtIndexPath的子类中的layoutSubviews以进行重新定心(这在WWDC 2011视频中展示,“高级滚动视图技术”)。这是控制器类,它具有集合视图(在IB中设置)作为子视图:

UICollectionView

这是#import "ViewController.h" #import "MultpleLineLayout.h" #import "DataCell.h" @interface ViewController () @property (weak,nonatomic) IBOutlet UICollectionView *collectionView; @property (strong,nonatomic) NSArray *theData; @end @implementation ViewController - (void)viewDidLoad { self.theData = @[@[@"1",@"2",@"3",@"4",@"5"], @[@"6",@"7",@"8",@"9",@"10"],@[@"11",@"12",@"13",@"14",@"15"],@[@"16",@"17",@"18",@"19",@"20"]]; MultpleLineLayout *layout = [[MultpleLineLayout alloc] init]; self.collectionView.collectionViewLayout = layout; self.collectionView.showsHorizontalScrollIndicator = NO; self.collectionView.showsVerticalScrollIndicator = NO; layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; self.view.backgroundColor = [UIColor blackColor]; [self.collectionView registerClass:[DataCell class] forCellWithReuseIdentifier:@"DataCell"]; [self.collectionView reloadData]; } - (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section { return 20; } - (NSInteger)numberOfSectionsInCollectionView: (UICollectionView *)collectionView { return 16; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { DataCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"DataCell" forIndexPath:indexPath]; cell.label.text = self.theData[indexPath.section %4][indexPath.row %5]; return cell; } - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { // UICollectionViewCell *item = [collectionView cellForItemAtIndexPath:indexPath]; NSLog(@"%@",indexPath); } 子类:

UICollectionViewFlowLayout

最后,这是#define space 5 #import "MultpleLineLayout.h" @implementation MultpleLineLayout { // a subclass of UICollectionViewFlowLayout NSInteger itemWidth; NSInteger itemHeight; } -(id)init { if (self = [super init]) { itemWidth = 60; itemHeight = 60; } return self; } -(CGSize)collectionViewContentSize { NSInteger xSize = [self.collectionView numberOfItemsInSection:0] * (itemWidth + space); // "space" is for spacing between cells. NSInteger ySize = [self.collectionView numberOfSections] * (itemHeight + space); return CGSizeMake(xSize, ySize); } - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path { UICollectionViewLayoutAttributes* attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:path]; attributes.size = CGSizeMake(itemWidth,itemHeight); int xValue = itemWidth/2 + path.row * (itemWidth + space); int yValue = itemHeight + path.section * (itemHeight + space); attributes.center = CGPointMake(xValue, yValue); return attributes; } -(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect { NSInteger minRow = (rect.origin.x > 0)? rect.origin.x/(itemWidth + space) : 0; // need to check because bounce gives negative values for x. NSInteger maxRow = rect.size.width/(itemWidth + space) + minRow; NSMutableArray* attributes = [NSMutableArray array]; for(NSInteger i=0 ; i < self.collectionView.numberOfSections; i++) { for (NSInteger j=minRow ; j < maxRow; j++) { NSIndexPath* indexPath = [NSIndexPath indexPathForItem:j inSection:i]; [attributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]]; } } return attributes; } 的子类:

UICollectionView

答案 1 :(得分:7)

@updated for swift 3并更改了如何计算maxRow,否则最后一列被截断并可能导致错误

import UIKit

class NodeMap : UICollectionViewController {
    var rows = 10
    var cols = 10

    override func viewDidLoad(){
        self.collectionView!.collectionViewLayout = NodeLayout(itemWidth: 400.0, itemHeight: 300.0, space: 5.0)
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return rows
    }

    override func numberOfSections(in collectionView: UICollectionView) -> Int {
        return cols
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        return collectionView.dequeueReusableCell(withReuseIdentifier: "node", for: indexPath)
    }
}

class NodeLayout : UICollectionViewFlowLayout {
    var itemWidth : CGFloat
    var itemHeight : CGFloat
    var space : CGFloat
    var columns: Int{
        return self.collectionView!.numberOfItems(inSection: 0)
    }
    var rows: Int{
        return self.collectionView!.numberOfSections
    }

    init(itemWidth: CGFloat, itemHeight: CGFloat, space: CGFloat) {
        self.itemWidth = itemWidth
        self.itemHeight = itemHeight
        self.space = space
        super.init()
    }

    required init(coder aDecoder: NSCoder) {
        self.itemWidth = 50
        self.itemHeight = 50
        self.space = 3
        super.init()
    }

    override var collectionViewContentSize: CGSize{
        let w : CGFloat = CGFloat(columns) * (itemWidth + space)
        let h : CGFloat = CGFloat(rows) * (itemHeight + space)
        return CGSize(width: w, height: h)
    }

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
        let x : CGFloat = CGFloat(indexPath.row) * (itemWidth + space)
        let y : CGFloat = CGFloat(indexPath.section) + CGFloat(indexPath.section) * (itemHeight + space)
        attributes.frame = CGRect(x: x, y: y, width: itemWidth, height: itemHeight)
        return attributes
    }

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let minRow : Int = (rect.origin.x > 0) ? Int(floor(rect.origin.x/(itemWidth + space))) : 0
        let maxRow : Int = min(columns - 1, Int(ceil(rect.size.width / (itemWidth + space)) + CGFloat(minRow)))
        var attributes : Array<UICollectionViewLayoutAttributes> = [UICollectionViewLayoutAttributes]()
        for i in 0 ..< rows {
            for j in minRow ... maxRow {
                attributes.append(self.layoutAttributesForItem(at: IndexPath(item: j, section: i))!)
            }
        }
        return attributes
    }
}

答案 2 :(得分:2)

@ rdelmar的回答就像一个魅力,但我需要迅速做到。这是转换:)

class NodeMap : UICollectionViewController {
    @IBOutlet var activateNodeButton : UIBarButtonItem?
    var rows = 10
    var cols = 10
    override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return rows
    }
    override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
        return cols
    }
    override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        return collectionView.dequeueReusableCellWithReuseIdentifier("node", forIndexPath: indexPath)
    }
    override func viewDidLoad() {
        self.collectionView!.collectionViewLayout = NodeLayout(itemWidth: 100.0, itemHeight: 100.0, space: 5.0)
    }
}

class NodeLayout : UICollectionViewFlowLayout {
    var itemWidth : CGFloat
    var itemHeight : CGFloat
    var space : CGFloat
    init(itemWidth: CGFloat, itemHeight: CGFloat, space: CGFloat) {
        self.itemWidth = itemWidth
        self.itemHeight = itemHeight
        self.space = space
        super.init()
    }
    required init(coder aDecoder: NSCoder) {
        self.itemWidth = 50
        self.itemHeight = 50
        self.space = 3
        super.init()
    }
    override func collectionViewContentSize() -> CGSize {
        let w : CGFloat = CGFloat(self.collectionView!.numberOfItemsInSection(0)) * (itemWidth + space)
        let h : CGFloat = CGFloat(self.collectionView!.numberOfSections()) * (itemHeight + space)
        return CGSizeMake(w, h)
    }
    override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
        let attributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)
        let x : CGFloat = CGFloat(indexPath.row) * (itemWidth + space)
        let y : CGFloat = CGFloat(indexPath.section) + CGFloat(indexPath.section) * (itemHeight + space)
        attributes.frame = CGRectMake(x, y, itemWidth, itemHeight)
        return attributes
    }
    override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
        let minRow : Int = (rect.origin.x > 0) ? Int(floor(rect.origin.x/(itemWidth + space))) : 0
        let maxRow : Int = Int(floor(rect.size.width/(itemWidth + space)) + CGFloat(minRow))
        var attributes : Array<UICollectionViewLayoutAttributes> = [UICollectionViewLayoutAttributes]()
        for i in 0...self.collectionView!.numberOfSections()-1 {
            for j in minRow...maxRow {
                attributes.append(self.layoutAttributesForItemAtIndexPath(NSIndexPath(forItem: j, inSection: i)))
            }
        }
        return attributes
    }
}

答案 3 :(得分:1)

重置contentOffset可能是到目前为止找到的最佳解决方案。 infinite scrolling final result

应该采取一些措施来实现这一目标:

  1. 在原始数据集的左侧和右侧填充额外的项目以实现更大的可滚动区域;这类似于具有大的重复数据集,但差异是数量;
  2. 开始时,计算集合视图的contentOffset以仅显示原始数据集(以黑色矩形绘制);
  3. 当用户向右滚动并且contentOffset达到触发值时,我们重置contentOffset以显示相同的视觉效果;但实际上不同的数据;当用户向左滚动时,使用相同的逻辑。
  4. enter image description here

    因此,繁重的工作是计算左右两侧应填充的物品数量。如果你看一下插图,你会发现至少有一个额外的项目屏幕应该在左边填充,另外一个额外的屏幕在右边。填充的确切金额取决于原始数据集中的项目数以及项目大小的大小。

    我写了一篇关于这个解决方案的帖子:

    http://www.awsomejiang.com/2018/03/24/Infinite-Scrolling-and-the-Tiling-Logic/