UICollectionView 中的 UICollectionViewFlowLayout - 每个部分中的动态单元格数

Posted

技术标签:

【中文标题】UICollectionView 中的 UICollectionViewFlowLayout - 每个部分中的动态单元格数【英文标题】:UICollectionViewFlowLayout in UICollectionView - Dynamic number of cells in each section 【发布时间】:2015-03-04 15:15:35 【问题描述】:

所以我正在尝试使用 UICollectionView 构建类似 EPG 的视图。这是我第一次使用这个框架。类似this。

我很快就注意到我需要实现一个自定义布局来实现这个 EPG。我关注了这个guide。

我需要“粘贴”集合视图的第一列和第一行,并让 EPG 水平和垂直滚动。虽然本教程很棒,但我在调整它以适应我的规范时遇到了问题。即本教程将每个部分中的列声明为等宽。而我要求我的列在每个部分中都是动态的。例如:每行本身就是一个部分,因此每行的高度将保持不变,但每行中的单元格将具有不同的宽度,具体取决于程序在每个单独的集合视图单元格中显示的长度。

任何人都可以帮助我或指出正确的方向如何使用以下代码实现这一点:

- (void)prepareLayout

    if ([self.collectionView numberOfSections] == 0) 
        return;
    

    NSUInteger column = 0; // Current column inside row
    CGFloat xOffset = 0.0;
    CGFloat yOffset = 0.0;
    CGFloat contentWidth = 0.0; // To determine the contentSize
    CGFloat contentHeight = 0.0; // To determine the contentSize

    if (self.itemAttributes.count > 0)  // We don't enter in this if statement the first time, we enter the following times
        for (int section = 0; section < [self.collectionView numberOfSections]; section++) 
            NSUInteger numberOfItems = [self.collectionView numberOfItemsInSection:section];
            for (NSUInteger index = 0; index < numberOfItems; index++) 
                if (section != 0 && index != 0)  // This is a content cell that shouldn't be sticked
                    continue;
                
                UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:section]];
                if (section == 0)  // We stick the first row
                    CGRect frame = attributes.frame;
                    frame.origin.y = self.collectionView.contentOffset.y;
                    attributes.frame = frame;

                
                if (index == 0)  // We stick the first column
                    CGRect frame = attributes.frame;
                    frame.origin.x = self.collectionView.contentOffset.x;
                    attributes.frame = frame;
                
            
        

        return;
    

    // The following code is only executed the first time we prepare the layout
    self.itemAttributes = [@[] mutableCopy];
    self.itemsSize = [@[] mutableCopy];

    // Tip: If we don't know the number of columns we can call the following method and use the NSUInteger object instead of the NUMBEROFCOLUMNS macro
    // NSUInteger numberOfItems = [self.collectionView numberOfItemsInSection:section];

    // We calculate the item size of each column
    if (self.itemsSize.count != NUMBEROFCOLUMNS) 
        [self calculateItemsSize];
    

    // We loop through all items
    for (int section = 0; section < [self.collectionView numberOfSections]; section++) 
        NSMutableArray *sectionAttributes = [@[] mutableCopy];

        for (NSUInteger index = 0; index < NUMBEROFCOLUMNS; index++) 
            CGSize itemSize = [self.itemsSize[index] CGSizeValue];

            // We create the UICollectionViewLayoutAttributes object for each item and add it to our array.
            // We will use this later in layoutAttributesForItemAtIndexPath:
            NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:section];
            UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
            attributes.frame = CGRectIntegral(CGRectMake(xOffset, yOffset, itemSize.width, itemSize.height));

            if (section == 0 && index == 0) 
                attributes.zIndex = 1024; // Set this value for the first item (Sec0Row0) in order to make it visible over first column and first row
             else if (section == 0 || index == 0) 
                attributes.zIndex = 1023; // Set this value for the first row or section in order to set visible over the rest of the items
            
            if (section == 0) 
                CGRect frame = attributes.frame;
                frame.origin.y = self.collectionView.contentOffset.y;
                attributes.frame = frame; // Stick to the top
            
            if (index == 0) 
                CGRect frame = attributes.frame;
                frame.origin.x = self.collectionView.contentOffset.x;
                attributes.frame = frame; // Stick to the left
            

            [sectionAttributes addObject:attributes];

            xOffset = xOffset+itemSize.width;
            column++;

            // Create a new row if this was the last column
            if (column == NUMBEROFCOLUMNS) 
                if (xOffset > contentWidth) 
                    contentWidth = xOffset;
                

                // Reset values
                column = 0;
                xOffset = 0;
                yOffset += itemSize.height;
            
        
        [self.itemAttributes addObject:sectionAttributes];
    

    // Get the last item to calculate the total height of the content
    UICollectionViewLayoutAttributes *attributes = [[self.itemAttributes lastObject] lastObject];
    contentHeight = attributes.frame.origin.y+attributes.frame.size.height;
    self.contentSize = CGSizeMake(contentWidth, contentHeight);


- (CGSize)collectionViewContentSize

    return self.contentSize;


- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath

    return self.itemAttributes[indexPath.section][indexPath.row];



- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect

    NSMutableArray *attributes = [@[] mutableCopy];

    for (NSArray *section in self.itemAttributes) 

        [attributes addObjectsFromArray:[section filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(UICollectionViewLayoutAttributes *evaluatedObject, NSDictionary *bindings) 
            return CGRectIntersectsRect(rect, [evaluatedObject frame]);
        ]]];
    

    return attributes;


- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds

    return YES; // Set this to YES to call prepareLayout on every scroll



- (CGSize)sizeForItemWithColumnIndex:(NSUInteger)columnIndex

    NSLog(@"sizeForItemWithColumnIndex");
    NSString *text;
    for (NSUInteger index = 0; index < NUMBEROFCOLUMNS; index++) 


        text = @"asdf";

    


    CGSize size = [text sizeWithAttributes: @NSFontAttributeName:[UIFont fontWithName:@"HelveticaNeue" size:15]];
    NSLog(@"Size of %@", NSStringFromCGSize(size));
    if (columnIndex == 0) 
        size.width += 1; // In our design the first column should be the widest one
    
    return CGSizeMake([@(size.width + 50) floatValue], 40); // Extra space of 9px for all the items




- (void)calculateItemsSize

    NSLog(@"calculate item size");

    for (NSUInteger index = 0; index < NUMBEROFCOLUMNS; index++) 

        if (self.itemsSize.count <= index) 

            CGSize itemSize = [self sizeForItemWithColumnIndex:index];

            NSValue *itemSizeValue = [NSValue valueWithCGSize:itemSize];

            [self.itemsSize addObject:itemSizeValue];
        
    


@end

【问题讨论】:

【参考方案1】:

好的,我自己回答了这个问题,从头开始。在准备布局之前,我将节目和频道信息的 xml Web 服务加载到 SQLite 数据库中。使用第三方库 FMDB 提取行并根据每个程序运行的分钟数计算每个单元格的框架(xPos、yPos、cellWidth、cellHeight)。 (除了包含时间线的第一部分,以及每个部分中包含每个特定通道的图标的第一项。)每个计算我都保存到字典中以供布局参考。

- (void)calculateFramesForAllPrograms 

    NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
    NSInteger counterForDatabaseRow = 0;

    for (NSInteger section = 0; section < numOfChannels + EXTRA_ROW_FOR_TIME; section++) 

        if(section==0)

            for (NSInteger items = 0; items <=HOURS_IN_A_DAY + EXTRA_ROW_FOR_TIME; items++)

                if(items==0)

                    CGRect frame = CGRectMake(xPos, yPos, TILE_HEIGHT, TILE_HEIGHT);
                    NSString *rectString = NSStringFromCGRect(frame);
                    indexPath = [NSIndexPath indexPathForItem:items inSection:section];
                    //NSLog(@"INDEX PATH %@", indexPath);
                    self.frameInfo[indexPath] = rectString;

                    xPos = xPos+TILE_HEIGHT;

                else 

                    CGRect frame = CGRectMake(xPos, yPos, self.timeTile.width, self.timeTile.height);
                    NSString *rectString = NSStringFromCGRect(frame);
                    indexPath = [NSIndexPath indexPathForItem:items inSection:section];
                    //NSLog(@"INDEX PATH %@", indexPath);
                    self.frameInfo[indexPath] = rectString;

                    xPos = xPos+self.timeTile.width;

                
            

         else 

            xPos = 0;
            //NSLog(@"SECTION COUNT: %lu", section);
            NSString *query = [NSString stringWithFormat:@"SELECT COUNT(channel_code) AS channels FROM epg WHERE channel_code='%@", self.channelCodes[section-EXTRA_ROW_FOR_TIME]];
            NSString *completedQuery = [query stringByAppendingString:@"'"];
            //NSLog(@"string %@", completedQuery);
            db = [FMDatabase databaseWithPath:[Utility getDatabasePath]];
            [db open];
            NSUInteger itemCount = [db intForQuery:completedQuery];

                for (NSInteger item = 0; item <itemCount+EXTRA_ROW_FOR_TIME; item++) 

                    //NSLog(@"Item: %lu Item: %lu", item, itemCount);

                    if(item==0)

                        CGRect frame = CGRectMake(xPos, yPos, TILE_HEIGHT, TILE_HEIGHT);
                        NSString *rectString = NSStringFromCGRect(frame);
                        indexPath = [NSIndexPath indexPathForItem:item inSection:section];
                        //NSLog(@"INDEX PATH %@", indexPath);
                        self.frameInfo[indexPath] = rectString;

                        xPos = xPos+TILE_HEIGHT;

                    else 

                        if (item==itemCount)
                            //NSLog(@"LAST ITEM %@", self.endTimes[counter]);
                            isLastItem = TRUE;
                        

                        CGSize tileSize = [self sizeForProgramTile:counterForDatabaseRow];
                        //NSLog(@"TILE SIZE RETURNED %@", NSStringFromCGSize(tileSize));
                        CGRect frame = CGRectMake(xPos, yPos, tileSize.width, tileSize.height);
                        NSString *rectString = NSStringFromCGRect(frame);
                        indexPath = [NSIndexPath indexPathForItem:item inSection:section];
                        //NSLog(@"INDEX PATH %@", indexPath);
                        self.frameInfo[indexPath] = rectString;

                        xPos = xPos+tileSize.width;

                        counterForDatabaseRow++;

                    

                
        

        yPos += TILE_HEIGHT;
    
    //NSLog(@"FRAME INFO %@", self.frameInfo);

【讨论】:

你能做到同样的事情吗?我也在寻找相同的代码来根据频道节目持续时间使单元格宽度动态化。

以上是关于UICollectionView 中的 UICollectionViewFlowLayout - 每个部分中的动态单元格数的主要内容,如果未能解决你的问题,请参考以下文章

UITableViewCell内的iOS UICollectionView

iOS UICollectionView简单使用

UICollectionView 比 NavigationController 大

UICollectionView cellForRow 崩溃

滚动 UICollectionView 时保留 UICollectionViewCell

以编程方式创建 UICollectionView