iosuiscrollviewspringboard

iOS - Creating a SpringBoard screen


I'm trying so create a collection view that behaves like the iOS springboard screen. That means that I want to have a collection view, with all of my items, and to have a page controller underneath the grid (just like the springboard screen). I also want to implement the same edit mode, meaning that one my icons are in edit mode, I can drag them from one page to another, in such a way that a page doesn't have to be full. I've tried to set a single collection view to scroll horizontally, but it doesn't act as I want. first of all, the icons are added one below the other, instead of one beside the other. secondly, I don't have the page dots this way, and the user can't put icons on different pages I also tried to set a page controller, and put in every page the collection view. In this case, I get that while in edit mode, I can't go between screens. I guess that is because I have more that one collection view, and they don't interact with each other. I though of another way, of using a scroll view, and try to put a collection view inside that scroll view. But it sounds to me like a very not elegant solution.

Does anyone have an idea about how to create such a scene?

Edit: I think the answer for my problem is hiding in this question. It describes the same situation I want to have: a collection view, that is scrolled horizontally, with paging, and that the order of the icons is not flowing vertically, but also flowing horizontally. There is a partially good answer there. I say it's partially good since the answer describes a subclass of UICollectionViewFlowLayout, but in the sample project uploaded the subclass is of UICollectionViewLayout, and I think the whole point was to maintain the functions that the UICollectionViewFlowLayout has. This question was asked a year ago, so I believe someone has an answer, or at least, I hope.


Solution

  • I have partial solution to my question. I did use the answer (of Bogdan) to the linked question above. Here's the code:

    layout.h:

    #import <UIKit/UIKit.h>
    
    @interface V9Layout : UICollectionViewLayout
    
    @property (nonatomic, assign) NSInteger itemsInOneRow;
    @property (nonatomic, assign) NSInteger itemsInOneCol;
    @property (nonatomic, assign) CGSize itemSize;
    
    @end
    

    layout.m:

    #import "V9Layout.h"
    
    @interface V9Layout()
    
    -(void)calculateLayoutProperties;
    -(int)pagesInSection:(NSInteger)section;
    
    @property (nonatomic, strong) NSMutableArray *frames;
    @property (nonatomic, assign) CGFloat lineSpacing;
    @property (nonatomic, assign) CGFloat interitemSpacing;
    @property (nonatomic, assign) CGSize pageSize;    
    @end
    
    @implementation V9Layout
    
    @synthesize itemsInOneRow = _itemsInOneRow, itemsInOneCol = _itemsInOneCol, itemSize = _itemSize;
    @synthesize lineSpacing = _lineSpacing, interitemSpacing = _interitemSpacing;
    @synthesize frames = _frames;
    
    - (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.itemsInOneRow = 3;
        self.itemsInOneCol = 4;
        self.frames = [NSMutableArray array];
    }
    
    -(void)calculateLayoutProperties {
        self.pageSize = self.collectionView.bounds.size;
    
        // The width of all line spacings is equal to width of ONE item. The same for interitem spacing
    
        CGFloat itemWidth = self.pageSize.width / (self.itemsInOneRow + 1); // +1 because we all spaces make width of one additional item
        CGFloat itemHeight = self.pageSize.height / (self.itemsInOneCol + 1); // the same
        NSLog(@"calculateLayoutProperties item width: %f, item height: %f", itemWidth, itemHeight);
    
        self.itemSize = CGSizeMake(itemWidth, itemHeight);
        // this part is only to make a global parameter, because I'm using this data to create the size of the UICollectionViewCell. I think I need to use a delegate method for the UICollectionViewCell to ask the UICollectionViewLayout for the calculated size of cell.
        NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
    
        if (standardUserDefaults) {
            [standardUserDefaults setObject:[NSNumber numberWithFloat:itemWidth] forKey:@"cellWidth"];
            [standardUserDefaults setObject:[NSNumber numberWithFloat:itemHeight] forKey:@"cellHeight"];
            [standardUserDefaults synchronize];
        }
    
        // spacing for x
        self.lineSpacing = (self.pageSize.width - (self.itemsInOneRow * self.itemSize.width)) / (self.itemsInOneRow + 1);
        // spacing for y
        self.interitemSpacing = (self.pageSize.height - (self.itemsInOneCol * self.itemSize.height)) / (self.itemsInOneCol + 1);
    }
    
    -(int)pagesInSection:(NSInteger)section {
        NSLog(@"v9layout: numberOfItemsInSection is %ld", (long)[self.collectionView numberOfItemsInSection:section]);
        return ([self.collectionView numberOfItemsInSection:section] - 1) / (self.itemsInOneRow * self.itemsInOneCol)  + 1;
    }
    
    -(CGSize)collectionViewContentSize {
        // contentSize.width is equal to pages * pageSize.width
        NSInteger sections = 1;
        if ([self.collectionView respondsToSelector:@selector(numberOfSections)]) {
            sections = [self.collectionView numberOfSections];
        }
        int pages = 0;
        for (int section = 0; section < sections; section++) {
            pages = pages + [self pagesInSection:section];
        }
        return CGSizeMake(pages * self.pageSize.width, self.pageSize.height);
    }
    
    -(void)prepareLayout {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            [self calculateLayoutProperties];
        });
    
        [self.frames removeAllObjects];
    
        NSInteger sections = 1;
        if ([self.collectionView respondsToSelector:@selector(numberOfSections)]) {
            sections = [self.collectionView numberOfSections];
        }
        int pagesOffset = 0; // Pages that are used by prevoius sections
        int itemsInPage = self.itemsInOneRow * self.itemsInOneCol;
        for (int section = 0; section < sections; section++) {
            NSMutableArray *framesInSection = [NSMutableArray array];
            int pagesInSection = [self pagesInSection:section];
            int itemsInSection = [self.collectionView numberOfItemsInSection:section];
            for (int page = 0; page < pagesInSection; page++) {
                int itemsToAddToArray = itemsInSection - framesInSection.count;
                int itemsInCurrentPage = itemsInPage;
                if (itemsToAddToArray < itemsInPage) { // If there are less cells than expected (typically last page of section), we go only through existing cells.
                    itemsInCurrentPage = itemsToAddToArray;
                }
                for (int itemInPage = 0; itemInPage < itemsInCurrentPage; itemInPage++) {
                    CGFloat originX = (pagesOffset + page) * self.pageSize.width + self.lineSpacing + (itemInPage % self.itemsInOneRow) * (self.itemSize.width + self.lineSpacing);
                    CGFloat originY = self.interitemSpacing + (itemInPage / self.itemsInOneRow) * (self.itemSize.height + self.interitemSpacing);
                    CGRect itemFrame = CGRectMake(originX, originY, self.itemSize.width, self.itemSize.height);
                    [framesInSection addObject:NSStringFromCGRect(itemFrame)];
                }
            }
            [self.frames addObject:framesInSection];
    
            pagesOffset += pagesInSection;
        }
    }
    
    -(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
        NSMutableArray *attributes = [NSMutableArray array];
    
        NSInteger sections = 1;
        if ([self.collectionView respondsToSelector:@selector(numberOfSections)]) {
            sections = [self.collectionView numberOfSections];
        }
    
        int pagesOffset = 0;
        int itemsInPage = self.itemsInOneRow * self.itemsInOneCol;
        for (int section = 0; section < sections; section++) {
            int pagesInSection = [self pagesInSection:section];
            int itemsInSection = [self.collectionView numberOfItemsInSection:section];
            for (int page = 0; page < pagesInSection; page++) {
                CGRect pageFrame = CGRectMake((pagesOffset + page) * self.pageSize.width, 0, self.pageSize.width, self.pageSize.height);
    
                if (CGRectIntersectsRect(rect, pageFrame)) {
                    int startItemIndex = page * itemsInPage;
                    int itemsInCurrentPage = itemsInPage;
                    if (itemsInSection - startItemIndex < itemsInPage) {
                        itemsInCurrentPage = itemsInSection - startItemIndex;
                    }
                    for (int itemInPage = 0; itemInPage < itemsInCurrentPage; itemInPage++) {
                        UICollectionViewLayoutAttributes *itemAttributes = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:startItemIndex + itemInPage inSection:section]];
                        if (CGRectIntersectsRect(itemAttributes.frame, rect)) {
                            [attributes addObject:itemAttributes];
                        }
                    }
                }
            }
    
            pagesOffset += pagesInSection;
        }
        return attributes;
    }
    
    -(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
        UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
        attributes.frame = CGRectFromString(self.frames[indexPath.section][indexPath.item]);
        return attributes;
    }
    
    @end
    

    This implement the horizontal paging with order of cells one beside each other and not one underneath each other. I still want to add a drag and drop ability so that the user can change the order of cells. I poset this question about it. Hopefully, I'll find answer to this and update this answer.