iOS 6.0: UICollectionView doesn't respect clip

2019-03-16 04:25发布

问题:

Background

I am implementing a UICollectionView (for the first time) in an effort to achieve a paged horizontal scroll view of tiles. I'd like each tile to show in the center of the frame with it's sister tiles partially visible to the left and right (something like the page selector in the Safari app). I'm interested in using the UICollectionView to take advantage of built-in cell dequeueing and would rather not use a rotated UITableView.

Issue

The issue I'm finding is that when using pagingEnabled = YES and clipsToBounds = NO, the UICollectionView removes cells outside the collectionView frame (they're not in the visibleCells array) as soon as paging is complete. Can anyone provide advice on how to achieve the effect of displaying previews of the sister tiles while maintaining this basic setup? Or am I approaching this incorrectly?

Screenshots

start scrolling end

The scrolling screen is exactly correct. But in the start and end shots I want there to be green visible in the blue margins.

Here's what's in my AppDelegate.m (credit to tutsplus.com for the basic setup here):

#import "AppDelegate.h"

@interface ViewController : UICollectionViewController
@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"ID"];
    [self.view setBackgroundColor:[UIColor blueColor]];
    // pad the collection view by 20 px
    UIEdgeInsets padding = UIEdgeInsetsMake(20.0, 20.0, 20.0, 20.0);
    [self.collectionView setFrame:UIEdgeInsetsInsetRect(self.view.frame, padding)];
    // set pagingEnabled and clipsToBounds off
    [self.collectionView setPagingEnabled:YES];
    [self.collectionView setClipsToBounds:NO];
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return 5;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"ID" forIndexPath:indexPath];
    UILabel *label = [[UILabel alloc] initWithFrame:cell.bounds];
    label.textAlignment = NSTextAlignmentCenter;
    label.text = [NSString stringWithFormat:@"%d", indexPath.row];
    [label setBackgroundColor:[UIColor greenColor]];
    [cell.contentView addSubview:label];
    return cell;
}
@end

@implementation AppDelegate
{
    ViewController *vc;
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // setup the UICollectionViewFlowLayout
    UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
    layout.itemSize = CGSizeMake(280, 280);
    layout.minimumInteritemSpacing = 0;
    layout.minimumLineSpacing = 0;
    layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    // add a custom UICollectionViewController to the window
    vc = [[ViewController alloc] initWithCollectionViewLayout:layout];
    self.window.rootViewController = vc;
    self.window.backgroundColor = [UIColor yellowColor];
    [self.window makeKeyAndVisible];
    return YES;
}
@end

回答1:

Turns out the solution to this was actually quite simple. I just needed to overlap the UICollectionViewCell cells by enough pixels to have them still show within the collectionView's frame after the paged scrolling finishes. The relevent code was

layout.itemSize = CGSizeMake(300, 300);
layout.minimumLineSpacing = -20.0;

And I subclassed the UICollectionViewFlowLayout and overrode the (CGSize)collectionViewContentSize method to return the non-overlapped size of the cells.



回答2:

Many thanks for the tip about using a negative minimumLineSpacing. I created a tester application which uses a collection view cell loaded from a xib file. The cell has a transparent background and an “inner” view for the cell's content.

In this way, a custom flow layout is not necessary.

https://github.com/j4johnfox/CollectionViewTester



回答3:

I'm not an expert in collectionView, but it could be possibly do with this line in cellForItemAtIndexPath:

[cell.contentView addSubview:label];

Everytime it's called, another label subview is added to cell. Either check for an existing label or subclass UICollectionViewCell?



回答4:

You'll want to also override -pointInside:withEvent: to allow scroll gestures to start outside the frame of the collection view. I do this using a UIEdgeInsets property in my collection view subclass:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGRect extendedBounds = UIEdgeInsetsInsetRect(self.bounds, self.touchAreaInsets);

    return CGRectContainsPoint(extendedBounds, point);
}

If you don't need App Store safety, you can override _visibleBounds to avoid negative spacing hacks:

- (CGRect)_visibleBounds {
     return UIEdgeInsetsInsetRect(self.bounds, self.touchAreaInsets);
}

If you're not too pressed on code size and need App Store safety you could also subclass PSTCollectionView and possibly override visibleBoundRects for the same effect.