在iOS 7上创建像Evernote这样的弹性UICollectionView

cno*_*oon 5 objective-c ios uicollectionview

我一直在努力尝试重新创建Evernote在iOS 7中使用的弹性集合视图,我真的很接近它的工作.我设法创建了一个自定义集合视图流布局,当内容偏移y值位于集合视图边界之外时,它会修改布局属性转换.我正在修改layoutAttributesForElementsInRect方法中的布局属性,它的行为与预期的一样,只是当您点击滚动视图的底部时底部单元格会消失.拉动内容偏移量越多,单元格就越多.我认为细胞基本上被剪掉了.它不会发生在顶部,我希望在这两个地方看到相同的行为.这就是我的流程布局实现现在的样子.

@implementation CNStretchyCollectionViewFlowLayout
{
    BOOL        _transformsNeedReset;
    CGFloat     _scrollResistanceDenominator;
}

- (id)init
{
    self = [super init];
    if (self)
    {
        // Set up the flow layout parameters
        self.minimumInteritemSpacing = 10;
        self.minimumLineSpacing = 10;
        self.itemSize = CGSizeMake(320, 44);
        self.sectionInset = UIEdgeInsetsMake(10, 0, 10, 0);

        // Set up ivars
        _transformsNeedReset = NO;
        _scrollResistanceDenominator = 800.0f;
    }

    return self;
}

- (void)prepareLayout
{
    [super prepareLayout];
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    // Set up the default attributes using the parent implementation
    NSArray *items = [super layoutAttributesForElementsInRect:rect];

    // Compute whether we need to adjust the transforms on the cells
    CGFloat collectionViewHeight = self.collectionViewContentSize.height;
    CGFloat topOffset = 0.0f;
    CGFloat bottomOffset = collectionViewHeight - self.collectionView.frame.size.height;
    CGFloat yPosition = self.collectionView.contentOffset.y;

    // Update the transforms if necessary
    if (yPosition < topOffset)
    {
        // Compute the stretch delta
        CGFloat stretchDelta = topOffset - yPosition;
        NSLog(@"Stretching Top by: %f", stretchDelta);

        // Iterate through all the visible items for the new bounds and update the transform
        for (UICollectionViewLayoutAttributes *item in items)
        {
            CGFloat distanceFromTop = item.center.y;
            CGFloat scrollResistance = distanceFromTop / 800.0f;
            item.transform = CGAffineTransformMakeTranslation(0, -stretchDelta + (stretchDelta * scrollResistance));
        }

        // Update the ivar for requiring a reset
        _transformsNeedReset = YES;
    }
    else if (yPosition > bottomOffset)
    {
        // Compute the stretch delta
        CGFloat stretchDelta = yPosition - bottomOffset;
        NSLog(@"Stretching bottom by: %f", stretchDelta);

        // Iterate through all the visible items for the new bounds and update the transform
        for (UICollectionViewLayoutAttributes *item in items)
        {
            CGFloat distanceFromBottom = collectionViewHeight - item.center.y;
            CGFloat scrollResistance = distanceFromBottom / 800.0f;
            item.transform = CGAffineTransformMakeTranslation(0, stretchDelta + (-stretchDelta * scrollResistance));
        }

        // Update the ivar for requiring a reset
        _transformsNeedReset = YES;
    }
    else if (_transformsNeedReset)
    {
        NSLog(@"Resetting transforms");
        _transformsNeedReset = NO;
        for (UICollectionViewLayoutAttributes *item in items)
            item.transform = CGAffineTransformIdentity;
    }

    return items;
}

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
    // Compute whether we need to adjust the transforms on the cells
    CGFloat collectionViewHeight = self.collectionViewContentSize.height;
    CGFloat topOffset = 0.0f;
    CGFloat bottomOffset = collectionViewHeight - self.collectionView.frame.size.height;
    CGFloat yPosition = self.collectionView.contentOffset.y;

    // Handle cases where the layout needs to be rebuilt
    if (yPosition < topOffset)
        return YES;
    else if (yPosition > bottomOffset)
        return YES;
    else if (_transformsNeedReset)
        return YES;

    return NO;
}

@end
Run Code Online (Sandbox Code Playgroud)

我还拉上了项目,供人们试用.任何帮助将不胜感激,因为我是创建自定义集合视图布局的新手.这是它的链接:

https://dl.dropboxusercontent.com/u/2975688/StackOverflow/stretchy_collection_view.zip

感谢大家!

cno*_*oon 2

我能够解决这个问题。我不确定 iOS 中是否确实存在错误,但问题是单元格实际上是在集合视图的内容视图之外进行翻译的。一旦细胞移动得足够远,它就会被剪掉。我发现有趣的是,这不会发生在非视网膜显示器的模拟器中,但会发生在视网膜显示器上,这就是为什么我觉得这实际上可能是一个错误。

考虑到这一点,目前的解决方法是通过重写 collectionViewContentSize 方法向集合视图的顶部和底部添加填充。执行此操作后,如果向顶部添加填充,则还需要调整单元格的布局属性,以便它们位于正确的位置。最后一步是在集合视图本身上设置 contentInset 以调整填充。保留滚动指示器插图,因为它们很好。这是我的最终集合视图控制器和自定义流布局的实现。

CNStretchyCollectionViewController.m

@implementation CNStretchyCollectionViewController

static NSString *CellIdentifier = @"Cell";

- (void)viewDidLoad
{
    // Register the cell
    [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:CellIdentifier];

    // Tweak out the content insets
    CNStretchyCollectionViewFlowLayout *layout = (CNStretchyCollectionViewFlowLayout *) self.collectionViewLayout;
    self.collectionView.contentInset = layout.bufferedContentInsets;

    // Set the delegate for the collection view
    self.collectionView.delegate = self;
    self.collectionView.clipsToBounds = NO;

    // Customize the appearance of the collection view
    self.collectionView.backgroundColor = [UIColor whiteColor];
    self.collectionView.indicatorStyle = UIScrollViewIndicatorStyleDefault;
}

#pragma mark - UICollectionViewDataSource Methods

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

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
    if ([indexPath row] % 2 == 0)
        cell.backgroundColor = [UIColor orangeColor];
    else
        cell.backgroundColor = [UIColor blueColor];

    return cell;
}

@end
Run Code Online (Sandbox Code Playgroud)

CNStretchyCollectionViewFlowLayout.m

@interface CNStretchyCollectionViewFlowLayout ()

- (CGSize)collectionViewContentSizeWithoutOverflow;

@end

#pragma mark -

@implementation CNStretchyCollectionViewFlowLayout
{
    BOOL            _transformsNeedReset;
    CGFloat         _scrollResistanceDenominator;
    UIEdgeInsets    _contentOverflowPadding;
}

- (id)init
{
    self = [super init];
    if (self)
    {
        // Set up the flow layout parameters
        self.minimumInteritemSpacing = 10;
        self.minimumLineSpacing = 10;
        self.itemSize = CGSizeMake(320, 44);
        self.sectionInset = UIEdgeInsetsMake(10, 0, 10, 0);

        // Set up ivars
        _transformsNeedReset = NO;
        _scrollResistanceDenominator = 800.0f;
        _contentOverflowPadding = UIEdgeInsetsMake(100.0f, 0.0f, 100.0f, 0.0f);
        _bufferedContentInsets = _contentOverflowPadding;
        _bufferedContentInsets.top *= -1;
        _bufferedContentInsets.bottom *= -1;
    }

    return self;
}

- (void)prepareLayout
{
    [super prepareLayout];
}

- (CGSize)collectionViewContentSize
{
    CGSize contentSize = [super collectionViewContentSize];
    contentSize.height += _contentOverflowPadding.top + _contentOverflowPadding.bottom;
    return contentSize;
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    // Set up the default attributes using the parent implementation (need to adjust the rect to account for buffer spacing)
    rect = UIEdgeInsetsInsetRect(rect, _bufferedContentInsets);
    NSArray *items = [super layoutAttributesForElementsInRect:rect];

    // Shift all the items down due to the content overflow padding
    for (UICollectionViewLayoutAttributes *item in items)
    {
        CGPoint center = item.center;
        center.y += _contentOverflowPadding.top;
        item.center = center;
    }

    // Compute whether we need to adjust the transforms on the cells
    CGFloat collectionViewHeight = [self collectionViewContentSizeWithoutOverflow].height;
    CGFloat topOffset = _contentOverflowPadding.top;
    CGFloat bottomOffset = collectionViewHeight - self.collectionView.frame.size.height + _contentOverflowPadding.top;
    CGFloat yPosition = self.collectionView.contentOffset.y;

    // Update the transforms if necessary
    if (yPosition < topOffset)
    {
        // Compute the stretch delta
        CGFloat stretchDelta = topOffset - yPosition;
        NSLog(@"Stretching Top by: %f", stretchDelta);

        // Iterate through all the visible items for the new bounds and update the transform
        for (UICollectionViewLayoutAttributes *item in items)
        {
            CGFloat distanceFromTop = item.center.y - _contentOverflowPadding.top;
            CGFloat scrollResistance = distanceFromTop / _scrollResistanceDenominator;
            item.transform = CGAffineTransformMakeTranslation(0, -stretchDelta + (stretchDelta * scrollResistance));
        }

        // Update the ivar for requiring a reset
        _transformsNeedReset = YES;
    }
    else if (yPosition > bottomOffset)
    {
        // Compute the stretch delta
        CGFloat stretchDelta = yPosition - bottomOffset;
        NSLog(@"Stretching bottom by: %f", stretchDelta);

        // Iterate through all the visible items for the new bounds and update the transform
        for (UICollectionViewLayoutAttributes *item in items)
        {
            CGFloat distanceFromBottom = collectionViewHeight + _contentOverflowPadding.top - item.center.y;
            CGFloat scrollResistance = distanceFromBottom / _scrollResistanceDenominator;
            item.transform = CGAffineTransformMakeTranslation(0, stretchDelta + (-stretchDelta * scrollResistance));
        }

        // Update the ivar for requiring a reset
        _transformsNeedReset = YES;
    }
    else if (_transformsNeedReset)
    {
        NSLog(@"Resetting transforms");
        _transformsNeedReset = NO;
        for (UICollectionViewLayoutAttributes *item in items)
            item.transform = CGAffineTransformIdentity;
    }

    return items;
}

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
    return YES;
}

#pragma mark - Private Methods

- (CGSize)collectionViewContentSizeWithoutOverflow
{
    return [super collectionViewContentSize];
}

@end
Run Code Online (Sandbox Code Playgroud)

CNStretchyCollectionViewFlowLayout.h

@interface CNStretchyCollectionViewFlowLayout : UICollectionViewFlowLayout

@property (assign, nonatomic) UIEdgeInsets bufferedContentInsets;

@end
Run Code Online (Sandbox Code Playgroud)

实际上,我将把这个发布到 Github 上,一旦项目启动,我将发布一个指向该项目的链接。再次感谢大家!