在很多新闻客户端中都有一个可以排序新闻类别的功能。也用不少通过重写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);
效果图: