在很多新聞客戶端中都有一個可以排序新聞類別的功能。也用不少通過重寫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);
效果圖: