Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
This repository was archived by the owner on Sep 25, 2024. It is now read-only.

adow/WKPagesCollectionView

Open more actions menu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

50 Commits
 
 
 
 
 
 
 
 

Repository files navigation

WKPagesCollectionView

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

  • 有页面翻转的效果;
  • 点击一个页面变成正常的显示状态;
  • 往左划动会删除这个cell;
  • 可以在底部添加新的cell;

点击查看效果视频

效果视频]

##使用

  • 项目中添加 WKPagesCollectionView目录以及下面的WK.h, WKPagesCollectionView.h, WKPagesCollectionView.m, WKPagesCollectionViewCell.h, WKPagesCollectionViewCell.m, WKPagesCollectionViewFlowLayout.h, WKPagesCollectionViewFlowLayout.m;

  • 引用 WKPagesCollectionView;

  • 准备数据

      _array=[[NSMutableArray alloc]init];
      for (int a=0; a<=30; a++) {
          [_array addObject:[NSString stringWithFormat:@"button %d",a]];
      }
    
  • 创建collectionView

      _collectionView=[[[WKPagesCollectionView alloc]initWithPagesFlowLayoutAndFrame:CGRectMake(0.0f, 0.0f, self.view.frame.size.width, self.view.frame.size.height)] autorelease];
      _collectionView.dataSource=self;
      _collectionView.delegate=self;
      [_collectionView registerClass:[WKPagesCollectionViewCell class] forCellWithReuseIdentifier:@"cell"];
      [self.view addSubview:_collectionView];
      _collectionView.maskShow=YES;
    
  • 完成WKPagesCollectionViewDataSource(继承自UICollectionViewDataSource) 和 WKPagesCollectionViewDelegate(继承自UICollectionViewDelegate), 除了要完成UICollectionViewDataSource中提供数据的方法外,还要完成追加数据的方法-(void)willAppendItemInCollectionView:(WKPagesCollectionView *)collectionView 和删除数据的方法 -(void)collectionView:(WKPagesCollectionView *)collectionView willRemoveCellAtNSIndexPath:(NSIndexPath *)indexPath

      #pragma mark - UICollectionViewDataSource and UICollectionViewDelegate
      -(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
          return _array.count;
      }
      -(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
      //    NSLog(@"cellForItemAtIndexPath:%d",indexPath.row);
          static NSString* identity=@"cell";
          WKPagesCollectionViewCell* cell=(WKPagesCollectionViewCell*)[collectionView dequeueReusableCellWithReuseIdentifier:identity forIndexPath:indexPath];
          cell.collectionView=collectionView;
          UIImageView* imageView=[[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"image-0"]] autorelease];
          imageView.frame=self.view.bounds;
          [cell.cellContentView addSubview:imageView];
          UIButton* button=[UIButton buttonWithType:UIButtonTypeCustom];
          button.frame=CGRectMake(0, (indexPath.row+1)*10+100, 320, 50.0f);
          button.backgroundColor=[UIColor whiteColor];
          [button setTitleColor:[UIColor darkGrayColor] forState:UIControlStateNormal];
          [button setTitle:_array[indexPath.row] forState:UIControlStateNormal];
          [button addTarget:self action:@selector(onButtonTitle:) forControlEvents:UIControlEventTouchUpInside];
          [cell.cellContentView addSubview:button];
          return cell;
      }
      #pragma mark WKPagesCollectionViewDataSource
      ///追加数据
      -(void)willAppendItemInCollectionView:(WKPagesCollectionView *)collectionView{
          [_array addObject:@"new button"];
      }
      ///删除数据
      -(void)collectionView:(WKPagesCollectionView *)collectionView willRemoveCellAtNSIndexPath:(NSIndexPath *)indexPath{
          [_array removeObjectAtIndex:indexPath.row];
      }
    
  • 添加一个页面时的操作

      -(IBAction)onButtonAdd:(id)sender{
          [_collectionView appendItem];
      }
    
  • 展开显示一个具体的页面,这个在点击页面的时候会自动实现,也可以像下面这样代码调用;

      [_collectionView showCellToHighLightAtIndexPath:indexPath completion:^(BOOL finished) {
          NSLog(@"highlight completed");
      }];
    
  • 停止展开,回到普通的滚动模式

      -(IBAction)onButtonTitle:(id)sender{
          NSLog(@"button");
          [_collectionView dismissFromHightLightWithCompletion:^(BOOL finished) {
              NSLog(@"dismiss completed");
          }];
      }
    

##TODO

  • bug 每滚动几个时候顶上的那一个就会先看不见然后又突然出现了,还没想到原因 这个问题已经解决,@Nikolay Abelyashev 修复了这个bug,原因是由于WKPagesCollectionView的高度太小,而UICollectionView会把屏幕外的cell不在显示,在WKPagesCollectionView中,屏幕外的3个cell会被取消显示,解决的办法是修改WKPagesCollectionView的frame,使他高出window(这里添加了 topOfScreen:120.0f),然后把frame.origin.y也往上移动了那么多距离,这样WKPagesCollectionView其实就更大;

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

我使用了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) {
	                    
	                }];
	            });
	            
	        }];
	    });
	}

About

iOS7 Safari-like tabs

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published
Morty Proxy This is a proxified and sanitized view of the page, visit original site.