How do I alternate a UICollectiveViewCell's background color based on row?

穿精又带淫゛_ 提交于 2019-12-18 12:44:18

问题


I know this is fairly easy for UITableViewCells but I'm not sure how to approach this using a UICollectionView.

EDIT. Pictures for clarification. Text content of the cells are not the same here but they should be. In landscape:

In portrait:

I tried to naively switch the color of my cell's text label with the cell's background color based on the index path's row property in the cellForItemAtIndexPath: method. However Index Path's row property isn't really a row in UICollectionViewFlowLayout.


回答1:


Neither the collection view nor its layout will tell you a “row number” for items. You have to compute it yourself.

If all of your measurements are uniform (which is the case if you didn't implement any UICollectionViewDelegateFlowLayout methods), you can compute it pretty easily.

There are a few places you could do the computation and assign the color. I'm going to show you the “proper” place to do it: in the layout manager.

The “Knowing When to Subclass the Flow Layout” section of the Collection View Programming Guide for iOS tells you the basic things you need to do, but it's pretty bare bones, so I'll walk you through it.

Note: since layoutAttributes is a pretty cumbersome identifier, I usually use pose instead.

UICollectionViewLayoutAttributes subclass

First of all, we need a way to pass the background color from the layout manager to the cell. The right way to do that is by subclassing UICollectionViewLayoutAttributes. The interface just adds one property:

@interface MyLayoutAttributes : UICollectionViewLayoutAttributes

@property (nonatomic, strong) UIColor *backgroundColor;

@end

The implementation needs to implement the NSCopying protocol:

@implementation MyLayoutAttributes

- (instancetype)copyWithZone:(NSZone *)zone {
    MyLayoutAttributes *copy = [super copyWithZone:zone];
    copy.backgroundColor = self.backgroundColor;
    return copy;
}

@end

UICollectionViewCell subclass

Next, the cell needs to use that backgroundColor attributes. You probably already have a UICollectionViewCell subclass. You need to implement applyLayoutAttributes: in your subclass, like this:

@implementation MyCell

- (void)applyLayoutAttributes:(MyLayoutAttributes *)pose {
    [super applyLayoutAttributes:pose];
    if (!self.backgroundView) {
        self.backgroundView = [[UIView alloc] init];
    }
    self.backgroundView.backgroundColor = pose.backgroundColor;
}

@end

UICollectionViewFlowLayout subclass

Now you need to make a subclass of UICollectionViewFlowLayout that uses MyLayoutAttributes instead of UICollectionViewLayoutAttributes, and sets the backgroundColor of each MyLayoutAttributes correctly. Let's define the interface to have a property which is the array of colors to assign to rows:

@interface MyLayout : UICollectionViewFlowLayout

@property (nonatomic, copy) NSArray *rowColors;

@end

The first thing we need to do in our implementation is specify what layout attributes class we want it to use:

@implementation MyLayout

+ (Class)layoutAttributesClass {
    return [MyLayoutAttributes class];
}

Next, we need to override two methods of UICollectionViewLayout to set the backgroundColor property of each pose. We call on super to get the set of poses, and then use a helper method to set the background colors:

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSArray *poses = [super layoutAttributesForElementsInRect:rect];
    [self assignBackgroundColorsToPoses:poses];
    return poses;
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
    MyLayoutAttributes *pose = (MyLayoutAttributes *)[super layoutAttributesForItemAtIndexPath:indexPath];
    [self assignBackgroundColorsToPoses:@[ pose ]];
    return pose;
}

Here's the method that actually assigns the background colors:

- (void)assignBackgroundColorsToPoses:(NSArray *)poses {
    NSArray *rowColors = self.rowColors;
    int rowColorsCount = rowColors.count;
    if (rowColorsCount == 0)
        return;

    UIEdgeInsets insets = self.sectionInset;
    CGFloat lineSpacing = self.minimumLineSpacing;
    CGFloat rowHeight = self.itemSize.height + lineSpacing;
    for (MyLayoutAttributes *pose in poses) {
        CGFloat y = pose.frame.origin.y;
        NSInteger section = pose.indexPath.section;
        y -= section * (insets.top + insets.bottom) + insets.top;
        y += section * lineSpacing; // Fudge: assume each prior section had at least one cell
        int row = floorf(y / rowHeight);
        pose.backgroundColor = rowColors[row % rowColorsCount];
    }
}

Note that this method makes several assumptions:

  • It assumes the scroll direction is vertical.
  • It assumes all items are the same size.
  • It assumes all sections have the same insets and line spacings.
  • It assumes every section has at least one item.

The reason for the last assumption is that all rows in a section are followed by the minimum line spacing, except for the last row, which is followed by the bottom section inset. I need to know how many rows preceded the current item's row. I try to do that by dividing the Y coordinate by the height of a row… but the last row of each section is shorter than the others. Hence the fudging.

Applying Your New Classes

Anyway, now we need to put these new classes to use.

First, we need to use MyLayout instead of UICollectionViewFlowLayout. If you're setting up your collection view in code, just create an instead of MyLayout and assign it to the collection view. If you're setting things up in a storyboard, find the collection view's layout (it is in the storyboard outliner as a child of the collection view) and set its custom class to MyLayout.

Second, we need to assign an array of colors to the layout. If you're using a storyboard, you probably want to do this in viewDidLoad. You could ask the collection view for its layout and then cast it to MyLayout. I prefer to use an outlet of the proper type, connected to the layout object in the storyboard.

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.layout.rowColors = @[
        [UIColor lightGrayColor],
        [UIColor cyanColor],
    ];
}

Result

If you got everything set up correctly, you'll get a result like this:

and in landscape orientation it looks like this:

I've put my test project in this github repository.




回答2:


I don' think there's any good general way to do this without subclassing the flow layout. For a more specific case, you can use integer math combined with the modulus operator to get "rows" from the indexPath. So, if you had 5 items on each row, you could return either 0 or 1 from this expression:

NSInteger rowTest = (indexPath.row / 5) % 2;

Just test this value, and change your color accordingly. Of course, you'll have to change the expression on rotation since you'll have a different number of items on each row.




回答3:


Use the indexPath.section property to get a row.




回答4:


Something like this should do it. May take some tweaking, etc. Assuming that you are not using sections which would have to have some changes..

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController
<UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout>

@end

#import "ViewController.h"

#define WIDTH   200
#define HEIGHT  200
#define XMARGIN 50
#define YMARGIN 20

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UICollectionView *collectionView;
@property (assign, nonatomic) int numberOfCols;
@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    UINib* cellNib = [UINib nibWithNibName:@"CollectionCell" bundle:nil];
    [self.collectionView registerNib:cellNib forCellWithReuseIdentifier:@"CollectionCell"];    
}

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
    return 1;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return 6;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CollectionCell" forIndexPath:indexPath];
    NSLog(@"Row number %d",indexPath.row);
    int rem = (indexPath.row / self.numberOfCols);
    NSLog(@"col number %d", rem);
    if ( rem % 2 == 0 )
        cell.backgroundColor = [UIColor yellowColor];
    else
        cell.backgroundColor = [UIColor greenColor];
    return cell;
}

#pragma mark – UICollectionViewDelegateFlowLayout

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    self.numberOfCols = (collectionView.frame.size.width - (XMARGIN*2)) / (WIDTH);
    NSLog(@"Number of cols is %d",self.numberOfCols);

    return CGSizeMake(WIDTH,HEIGHT);
}

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
    return UIEdgeInsetsMake(XMARGIN, YMARGIN, XMARGIN, YMARGIN);
}

- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
    [self.collectionView reloadData];
}



回答5:


This is yucky, subject to breaking if there's a header, but at least this will get you going. In cellForItemAtIndexPath...

// dequeue cell

CGFloat originY = cell.frame.origin.y;

// if there are headers, then grab the header view and...
originY -= header.bounds.size.height * indexPath.section;

NSInteger row = originY / cell.bounds.size.height;
BOOL evenRow = row%2 == 0;


来源:https://stackoverflow.com/questions/18317405/how-do-i-alternate-a-uicollectiveviewcells-background-color-based-on-row

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!