Home

Awesome

WKPagesCollectionView

我尝试想做一个类似 iOS7 下的 safari tabs 页面那样的效果。

点击查看效果视频

效果视频]

##使用

##TODO

##实现的方式 ###实现滚动

我使用了UICollectionView来实现,本质上是一个垂直的列表,而主要的工作是来创造一个CollectionViewLayout, (我定义为WKPagesCollectionViewFlowLayout)每一个cell其实是和当前屏幕一样大小的,也就是说其实就是有一堆和屏幕一样大的cell错开折叠在一起,他们之间的间隔设置为self.minimumLineSpacing=-1*(self.itemSize.height-160.0f);不翻转cell时

为了实现翻转的效果,我在WKPagesCollectionViewFlowLayout中的 -(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect 中修改了transform3D。

看上去貌似比较简单,而如果所有的页面都是固定的角度的话,的确也没问题,但是我想像safari里那样在滚动时有点视差的效果,所以就为每个页面设置了不同的翻转角度了,其实就是在layoutAttributesForElementsInRect 中根据每个cell的位置来计算角度使得他们在滚动条滚动时有点不同的角度,下面这个是设置角度的方法:

	-(void)makeRotateTransformForAttributes:(UICollectionViewLayoutAttributes*)attributes{
	    attributes.zIndex=attributes.indexPath.row;///要设置zIndex,否则遮挡顺序会有编号
	    CGFloat distance=attributes.frame.origin.y-self.collectionView.contentOffset.y;
	    CGFloat normalizedDistance = distance / self.collectionView.frame.size.height;
	    normalizedDistance=fmaxf(normalizedDistance, 0.0f);
	    CGFloat rotate=RotateDegree+20.0f*normalizedDistance;
	    //CGFloat rotate=RotateDegree;
	    NSLog(@"makeRotateTransformForAttributes:row:%d,normalizedDistance:%f,rotate:%f",
	          attributes.indexPath.row,normalizedDistance,rotate);
	    ///角度大的会和角度小的cell交叉,即使设置zIndex也没有用,这里设置底部的cell角度越来越大
	    CATransform3D rotateTransform=WKFlipCATransform3DPerspectSimpleWithRotate(rotate);
	    attributes.transform3D=rotateTransform;
	    
	}
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path
{
    NSLog(@"layoutAttributesForItemAtIndexPath:%d",path.row);
    UICollectionViewLayoutAttributes* attributes=[super layoutAttributesForItemAtIndexPath:path];
    [self makeRotateTransformForAttributes:attributes];
    return attributes;
}
-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSLog(@"layoutAttributesForElementsInRect:%@",NSStringFromCGRect(rect));
    NSArray* array = [super layoutAttributesForElementsInRect:rect];
    for (UICollectionViewLayoutAttributes* attributes in array) {
        [self makeRotateTransformForAttributes:attributes];
    }
    return array;
}

现在运行的时候,滚动起来就和想要的效果差不多了,虽然不如safari那么细节完美,大概的意思是达到了。

##实现删除

后面我想做出safari那样按住其中一个cell往左滑动就删除的效果,在每个cell里面添加一个scrollView,然后scrollViewDidEndDragging 中达到一定距离的时候就触发删除好了,而UICollectionView中的performBatchUpdates就可以很好的完成删除的动画了。

删除时的效果

	-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
	    if (self.showingState==WKPagesCollectionViewCellShowingStateNormal){
	        if (scrollView.contentOffset.x>=90.0f){
	            NSIndexPath* indexPath=[self.collectionView indexPathForCell:self];
	            NSLog(@"delete cell at %d",indexPath.row);
	            //self.alpha=0.0f;
	            ///删除数据
	            id<WKPagesCollectionViewDataSource> pagesDataSource=(id<WKPagesCollectionViewDataSource>)self.collectionView.dataSource;
	            [pagesDataSource collectionView:(WKPagesCollectionView*)self.collectionView willRemoveCellAtNSIndexPath:indexPath];
	            ///动画
	            [self.collectionView performBatchUpdates:^{
	                [self.collectionView deleteItemsAtIndexPaths:@[indexPath,]];
	            } completion:^(BOOL finished) {
	                
	            }];
	        }
	    }
	}

为了添加和删除的时候动画的好看一点,我们还得修改 (UICollectionViewLayoutAttributes*)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath-(UICollectionViewLayoutAttributes*)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath

我在WKPagesCollectionViewFlowLayout 中添加了insertIndexPaths 和 deleteIndexPaths 来记录用来添加和删除的位置,因为这个两个回调在添加和删除时会被调用,而且不仅仅是针对正在添加或者删除的NSIndexPath,其他行也会被调用,而我们这里只要处理正在添加和删除的NSIndexPath;

	-(UICollectionViewLayoutAttributes*)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath{
	    UICollectionViewLayoutAttributes* attributes=[super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath];
	    NSLog(@"initialLayoutAttributesForAppearingItemAtIndexPath:%d",itemIndexPath.row);
	    if ([self.insertIndexPaths containsObject:itemIndexPath]){
	        if (!attributes)
	            attributes=[self layoutAttributesForItemAtIndexPath:itemIndexPath];
	        CATransform3D rotateTransform=WKFlipCATransform3DPerspectSimpleWithRotate(-90.0f);
	        attributes.transform3D=rotateTransform;
	    }
	    return attributes;
	}
	-(UICollectionViewLayoutAttributes*)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath{
	    NSLog(@"finalLayoutAttributesForDisappearingItemAtIndexPath:%d",itemIndexPath.row);
	    UICollectionViewLayoutAttributes* attributes=[super finalLayoutAttributesForDisappearingItemAtIndexPath:itemIndexPath];
	    if ([self.deleteIndexPaths containsObject:itemIndexPath]){
	        if (!attributes){
	            attributes=[self layoutAttributesForItemAtIndexPath:itemIndexPath];
	        }
	        CATransform3D moveTransform=CATransform3DMakeTranslation(-320.0f, 0.0f, 0.0f);
	        attributes.transform3D=CATransform3DConcat(attributes.transform3D, moveTransform);
	    }    
	    return attributes;
	    
	}

开始的时候会发生一些意想不到的动画效果,滚动到底部,然后在模拟器下按下command+t打开慢速动画的时候就看的很清楚了,往左滑动来删除最后两个cell,在删除时一开始的效果是正常的,而几乎在完成之后,会看到一闪,出现了两个cell在很奇怪的位置,然后又动画慢慢回到预订的位置。

现在的问题就是,如果我删除带有button-0,button-1,button-2,button-3这样的cell,动画是正常的,但是如果我删除最后几个cell,带有button-6,button-7,button-8的,就会出现意想不到的动画。

而且我发现如果我的cell的翻转角度如果是全部固定的,那在删除cell时是不会发生奇怪的动画的,我的makeRotateTransformForAttributes2中是指定了一个固定的角度。

####Fixed

后来终于知道问题在哪里了,只是由于contentSize计算错误导致内容区域不够所以在删除cell后又自动滚动时产生了奇怪的行为,所以-(CGSize)collectionViewContentSize中的高度一定要正确。

###实现添加页面

现在页面有两种状态,一种就是这种普通的滚动页面的状态,当点击某一个页面的时候,他会翻转到全屏显示,这时我称作是highlight。如果只是在普通的滚动状态下,会先滚动到屏幕地步,然后添加一个页面,之后又会把这个页面展开到highLight显示状态。而如果现在整个collectionView本身就已经有一个页面在hightLight了,那应该先退回到普通状态,再重复之前的添加页面过程。

下面是在WKPagesCollectionView中的添加页面的方法;

	///追加一个页面
	-(void)appendItem{
	    if (self.isHighLight){
	        [self dismissFromHightLightWithCompletion:^(BOOL finished) {
	            [self _addNewPage];
	        }];
	    }
	    else{
	        [self _addNewPage];
	    }
	}
	///添加一页
	-(void)_addNewPage{
	    int total=[self numberOfItemsInSection:0];
	    [self scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:total-1 inSection:0] atScrollPosition:UICollectionViewScrollPositionBottom animated:YES];
	    double delayInSeconds = 0.3f;
	    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
	    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
	        ///添加数据
	        [(id<WKPagesCollectionViewDataSource>)self.dataSource willAppendItemInCollectionView:self];
	        int lastRow=total;
	        NSIndexPath* insertIndexPath=[NSIndexPath indexPathForItem:lastRow inSection:0];
	        [self performBatchUpdates:^{
	            [self insertItemsAtIndexPaths:@[insertIndexPath]];
	        } completion:^(BOOL finished) {
	            double delayInSeconds = 0.3f;
	            dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
	            dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
	                [self showCellToHighLightAtIndexPath:insertIndexPath completion:^(BOOL finished) {
	                    
	                }];
	            });
	            
	        }];
	    });
	}