長按即可移動Cell的CollectionViewFlowLayout

在很多新聞客戶端中都有一個可以排序新聞類別的功能。也用不少通過重寫UIScrollView來實現的。這里主要說下通過UICollectionView怎么實現,準確的說是通過CollectionViewFlowLayout來實現。

  • 什么是UICollectionView

UICollectionView是一種新的數據展示方式,簡單來說可以把他理解成多列的UITableView(請一定注意這是UICollectionView的最最簡單的形式)。如果你用過iBooks的話,可能你還對書架布局有一定印象:一個虛擬書架上放著你下載和購買的各類圖書,整齊排列

  • UICollectionView結構

    • Cells 用于展示內容的主體,對于不同的cell可以指定不同尺寸和不同的內容
    • Supplementary Views 追加視圖 如果你對UITableView比較熟悉的話,可以理解為每個Section的Header或者Footer,用來標記每個section的view
    • Decoration Views 裝飾視圖,這是每個section的背景
  • UICollectionViewLayout

UICollectionViewLayout是造就UICollectionView和UITableView最大不同的地方。UICollectionViewLayout可以說是UICollectionView的大腦和中樞,它負責了將各個cell、Supplementary View和Decoration Views進行組織,為它們設定各自的屬性,包括但不限于:

  • 位置
  • 尺寸
  • 透明度
  • 層級關系
  • 形狀
  • 等等等等…
  • Layout決定了UICollectionView是如何顯示在界面上的。在展示之前,一般需要生成合適的UICollectionViewLayout子類對象,并將其賦予CollectionView的collectionViewLayout屬性。

長按可移動Cell的UICollectionViewLayout

1、通過UICollectionViewLayout給UICollectionView添加長按事件:主要通過監聽layout是否被添加到collectionView中。

-(void)addCollectionCreatedObserver{
  [self addObserver:self forKeyPath:@"collectionView" options:NSKeyValueObservingOptionNew context:nil];
}

2、添加長按手勢

-(void)setupCollectionView{
  self.longGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handlerLongPressGesture:)];
  self.longGestureRecognizer.delegate = self;
  [self.collectionView addGestureRecognizer:self.longGestureRecognizer];
}

3、處理手勢

-(void)handlerLongPressGesture:(UILongPressGestureRecognizer *)gestureRecognizer{
  switch (gestureRecognizer.state) {
      case UIGestureRecognizerStateBegan:
          [self startLongPressGesture:gestureRecognizer];
          break;
      case UIGestureRecognizerStateChanged:
          [self moveLongPressGesture:gestureRecognizer];
          break;
      case UIGestureRecognizerStateCancelled:
      case UIGestureRecognizerStateEnded:
          [self endLongPressGesture:gestureRecognizer];
          break;
      default:
          break;
  }
}

4、處理手勢開始移動操作,主要記錄移動的起點,對應的Cell,并對當前Cell做快照,以便移動

  self.startPoint = [gestureRecognizer locationInView:self.collectionView];
  self.selectedMoveIndexPath = currentIndexPath;
  UICollectionViewCell *collectionViewCell = [self.collectionView cellForItemAtIndexPath:self.selectedMoveIndexPath];
  self.currentMoveView = [[UIView alloc] initWithFrame:collectionViewCell.frame];
  collectionViewCell.highlighted = YES;
  
  UIView *highlightedISnapshotView = [collectionViewCell MT_snapshotView];
  highlightedISnapshotView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  highlightedISnapshotView.alpha = 1.0;
  
  collectionViewCell.highlighted = NO;
  UIView *snapshotView = [collectionViewCell MT_snapshotView];
  snapshotView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  snapshotView.alpha = 0.0;
  
  [self.currentMoveView addSubview:snapshotView];
  [self.currentMoveView addSubview:highlightedISnapshotView];
  [self.collectionView addSubview:self.currentMoveView];
  self.currentViewCenter = self.currentMoveView.center;

5、處理手勢正在移動操作,主要記錄坐標變動,并移動快照視圖位置:

  CGPoint currentPoint = [gestureRecognizer locationInView:self.collectionView];
  self.currentPoint = CGPointMake(currentPoint.x - self.startPoint.x, currentPoint.y - self.startPoint.y);
  CGPoint viewCenter = self.currentMoveView.center = MT_CGPointAdd(self.currentViewCenter, self.currentPoint);

刷新CollectionView

  NSIndexPath *newIndexPath = [self.collectionView indexPathForItemAtPoint:self.currentMoveView.center];
  NSIndexPath *preIndexPath = self.selectedMoveIndexPath;
  if(newIndexPath == nil || [newIndexPath isEqual:preIndexPath]){
      return;
  }
  self.selectedMoveIndexPath = newIndexPath;
  
  if ([self.collectionView.dataSource respondsToSelector:@selector(collectionView:moveItemAtIndexPath:toIndexPath:)]) {
      [self.collectionView.dataSource collectionView:self.collectionView moveItemAtIndexPath:preIndexPath toIndexPath:newIndexPath];
  }
  __weak typeof(self) weakSelf = self;
  [self.collectionView performBatchUpdates:^{
      [weakSelf.collectionView deleteItemsAtIndexPaths:@[preIndexPath]];
      [weakSelf.collectionView insertItemsAtIndexPaths:@[newIndexPath]];
  } completion:^(BOOL finished) {
      
  }];

6、處理手勢結束移動。重置記錄:

NSIndexPath *currentIndexPath = self.selectedMoveIndexPath;
self.selectedMoveIndexPath = nil;
self.currentViewCenter = CGPointZero;
[self.currentMoveView removeFromSuperview];
self.currentMoveView = nil;
[self invalidateLayout];
      

7、重載layoutAttributesForElementsInRect:

  -(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
    NSArray *layoutAttributesForElementsInRect = [super layoutAttributesForElementsInRect:rect];
    for (UICollectionViewLayoutAttributes *layoutAttributes in layoutAttributesForElementsInRect) {
        if(layoutAttributes.representedElementCategory == UICollectionElementCategoryCell){
            [self applyLayoutAttributes:layoutAttributes];
        }
    }
    return layoutAttributesForElementsInRect;
}

8、重載layoutAttributesForItemAtIndexPath:

-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
    UICollectionViewLayoutAttributes *layoutAttributes = [super layoutAttributesForItemAtIndexPath:indexPath];
    if(layoutAttributes.representedElementCategory == UICollectionElementCategoryCell){
        [self applyLayoutAttributes:layoutAttributes];
    }
    return layoutAttributes;
}

9、隱藏當前選擇的Cell

-(void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
  if([layoutAttributes.indexPath isEqual:self.selectedMoveIndexPath]){
      layoutAttributes.hidden = YES;
  }
}

移動Cell時可通知滾動UICollectionView

添加CADisplayLink,來主動觸發UICollectionView滾動。

1、創建CADisplayLink

    self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleScroll:)];
    
    [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];

2、處理屏幕刷新事件:

    CGSize frameSize = self.collectionView.bounds.size;
    CGSize contentSize = self.collectionView.contentSize;
    CGPoint contentOffset = self.collectionView.contentOffset;
    UIEdgeInsets contentInset = self.collectionView.contentInset;
    // Important to have an integer `distance` as the `contentOffset` property automatically gets rounded
    // and it would diverge from the view's center resulting in a "cell is slipping away under finger"-bug.
    CGFloat distance = rint(self.scrollingSpeed * displayLink.duration);
    CGPoint translation = CGPointZero;
    ...
    self.collectionView.contentOffset = MT_CGPointAdd(contentOffset, translation);

效果圖:

Untitled.gif

完整源碼--》Github

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,180評論 4 61
  • 什么是UICollectionView? UICollectionView是一種新的數據展示方式,簡單來說可以把他...
    凌峰Mical閱讀 43,387評論 11 201
  • 最近將 UICollectionView 進行了一個全面的學習及總結,參考了網上大量的文章,把官方文檔進行了大概翻...
    varlarzh閱讀 21,530評論 3 94
  • 地上的劍還在錚鳴, 殘陽如血,紅了雙眼, 聽山風四蕩,散了這一世英明。 從來無休離愁恨愛, 焚心流淚,終究無奈, ...
    小王爺小王爺閱讀 253評論 0 1
  • 1.過濾器創建 了解過濾器常見配置含義 filter-mapping標簽里面還有許多參數,可以參考學習博客特別注意...
    丁白一閱讀 242評論 0 0