View with continuous scroll; both horizontal and vertical

后端 未结 4 537
迷失自我
迷失自我 2020-11-28 03:02

I have been struggling with this assignment for quite some time now. What I would like to develop is a scrollview or collectionview which scrolls continuously both vertical

相关标签:
4条回答
  • 2020-11-28 03:42

    Resetting the contentOffset probably is the best solution figured out so far.

    A few steps should be taken to achieve this:

    1. Pad extra items at both the left and right side of the original data set to achieve larger scrollable area; This is similar to having a large duplicated data set, but difference is the amount;
    2. At start, the collection view’s contentOffset is calculated to show only the original data set (drawn in black rectangles);
    3. When the user scrolls right and contentOffset hits the trigger value, we reset contentOffset to show same visual results; but actually different data; When the user scrolls left, the same logic is used.

    So, the heavy lifting is in calculating how many items should be padded both on the left and right side. If you take a look at the illustration, you will find that a minimum of one extra screen of items should be padded on left and also, another extra screen on the right. The exact amount padded depends on how many items are in the original data set and how large your item size is.

    I wrote a post on this solution:

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

    0 讨论(0)
  • 2020-11-28 03:43

    @rdelmar's answer worked like a charm, but I needed to do it in swift. Here's the conversion :)

    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
        }
    }
    
    0 讨论(0)
  • 2020-11-28 03:54

    @updated for swift 3 and changed how the maxRow is calculated otherwise the last column is cutoff and can cause errors

    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
        }
    }
    
    0 讨论(0)
  • 2020-11-28 03:56

    You can get infinite scrolling, by using the technique of re-centering the UIScrollView after you get a certain distance away from the center. First, you need to make the contentSize big enough that you can scroll a bit, so I return 4 times the number of items in my sections and 4 times the number of sections, and use the mod operator in the cellForItemAtIndexPath method to get the right index into my array. You then have to override layoutSubviews in a subclass of UICollectionView to do the re-centering (this is demonstrated in the WWDC 2011 video, "Advanced Scroll View Techniques"). Here is the controller class that has the collection view (set up in IB) as a subview:

    #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);
    
    }
    

    Here is the UICollectionViewFlowLayout subclass:

    #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;
    }
    

    And finally, here is the subclass of UICollectionView:

    -(void)layoutSubviews {
        [super layoutSubviews];
        CGPoint currentOffset = self.contentOffset;
        CGFloat contentWidth = self.contentSize.width;
        CGFloat contentHeight = self.contentSize.height;
        CGFloat centerOffsetX = (contentWidth - self.bounds.size.width)/ 2.0;
        CGFloat centerOffsetY = (contentHeight - self.bounds.size.height)/ 2.0;
        CGFloat distanceFromCenterX = fabsf(currentOffset.x - centerOffsetX);
        CGFloat distanceFromCenterY = fabsf(currentOffset.y - centerOffsetY);
    
        if (distanceFromCenterX > contentWidth/4.0) { // this number of 4.0 is arbitrary
            self.contentOffset = CGPointMake(centerOffsetX, currentOffset.y);
        }
        if (distanceFromCenterY > contentHeight/4.0) {
            self.contentOffset = CGPointMake(currentOffset.x, centerOffsetY);
        }
    }
    
    0 讨论(0)
提交回复
热议问题