我正在使用UICollectionView
,其最低开发目标为iOS9
,并发现可以支持interactive movement
的拖放。一切正常但是将单元格作为目标部分中的最后一个单元格而不能正常工作。
注意:我不想要iOS11
拖放解决方案,因为我的应用程序的目标是iOS9。在每个部分的末尾创建一个虚拟单元格并不是一个直接的解决方案。
请查看图片以直观地解决问题。
提前致谢。
答案 0 :(得分:1)
我遇到了同样的问题,最终设法完成了它。我使用Apple文档和多篇关于UICollectionView的博客文章在各部分甚至无效的indexPaths之间构建一个功能齐全的Drag'N Drop功能。
首先,您必须禁用Apple提供的自动Drag'N Drop功能,因为您将使用自己的GestureRecognizer。
因此,请在viewDidLoad
:
// deactivate default gesture recognizer for drag'n drop
self.installsStandardGestureForInteractiveMovement = NO;
向longPressGestureRecognizer
添加collectionView
,将UIGestureRecognizerDelegate
添加到您的viewController协议,并在viewDidLoad
中设置代理:
_longPressGestureRecognizer.delegate = self;
您现在必须实施longPressGestureRecognizer
操作:
// static variables to manage drag'n drop on invalid spaces
static NSIndexPath * longPressIndexPath = nil;
static CGPoint longPressLocation = (CGPoint) {0, 0};
- (IBAction)longPress:(UILongPressGestureRecognizer *)sender {
@autoreleasepool {
static BOOL isDragNDropping = NO;
longPressLocation = [sender locationInView:self.collectionView];
longPressIndexPath = [self.collectionView indexPathForItemAtPoint:longPressLocation];
// get the cell at indexPath (the one you long pressed)
UICollectionViewCell* cell = nil;
if (sender.state == UIGestureRecognizerStateChanged) {
NSLog(@"%s : STATE CHANGED", __FUNCTION__);
// Do stuff with cell
// Update the cell movement if the longPress location changes
if (isDragNDropping) {
[self.collectionView updateInteractiveMovementTargetPosition:longPressLocation];
} else {
// It's the first longPress movement, start the drag'n drop
if (longPressIndexPath) {
[self.collectionView beginInteractiveMovementForItemAtIndexPath:longPressIndexPath];
isDragNDropping = YES;
}
}
return;
}
if (sender.state == UIGestureRecognizerStateRecognized) {
NSLog(@"%s : STATE RECOGNIZED", __FUNCTION__);
// do stuff with the cell
// We were long pressing on a cell
if (isDragNDropping) {
[self.collectionView endInteractiveMovement];
}
isDragNDropping = NO;
return;
}
if (sender.state == UIGestureRecognizerStateBegan) {
NSLog(@"%s : STATE BEGAN", __FUNCTION__);
// do stuff with the newly selected cell
if (longPressIndexPath) {
cell = [self.collectionView cellForItemAtIndexPath: longPressIndexPath];
// You can add custom animations on the cell
[UIView animateWithDuration:0.35f animations:^{
[cell setTransform:CGAffineTransformMakeScale(1.2f, 1.2f)];
cell.layer.shadowColor = [UIColor blackColor].CGColor;
cell.layer.shadowRadius = 6.f;
cell.layer.shadowOpacity = 0.6f;
} completion:^(BOOL finished) {
}];
}
isDragNDropping = NO;
return;
}
}
}
基本上,此时你已经完成了Apple正在做的默认Drag'N Drop。
(NSIndexPath *)collectionView:(UICollectionView *)collectionView targetIndexPathForMoveFromItemAtIndexPath:(NSIndexPath *)originalIndexPath toProposedIndexPath:(NSIndexPath *)proposedIndexPath
它允许您绕过建议的indexPath到您选择的一个。
(如果您使用CoreData和NSFetchedResultsController作为您的数据源,您需要使用排名来对您的项目进行排序,并且此排名必须在此更改)
-(void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
要解决此问题,您必须保留longPressGestureRecognizer
位置和indexPath的记录。我已将这两个变量声明为静态,但ViewController的属性也可以。
这里的关键是在longPressGestureRecognizer
位置生成虚拟indexPath,而不是像这样建议的indexPath:
- (NSIndexPath *)collectionView:(UICollectionView *)collectionView targetIndexPathForMoveFromItemAtIndexPath:(NSIndexPath *)originalIndexPath toProposedIndexPath:(NSIndexPath *)proposedIndexPath
{
NSLog(@"%s", __FUNCTION__);
NSLog(@"original idxPath : %li - %li | proposed idxPath : %li - %li", originalIndexPath.section, originalIndexPath.item, proposedIndexPath.section, proposedIndexPath.item);
// The longPressIndexPath is valid, we are drag'n dropping on an valid location, keep the proposedIndexPath
if (longPressIndexPath != nil) {
return proposedIndexPath;
}
// The longPressIndexPath is nil, which means we are drag'n dropping on a new location (at the end of a section in most cases)
if (longPressIndexPath == nil) {
NSLog(@"%s virtualIndexPath", __FUNCTION__);
// This part is not actually needed,
// I have added a way to accept drops on header/footer, and add the item to the corresponding section
// Build an set with each sectionIndexPaths
NSMutableSet<NSIndexPath*>* visiblesSectionIndexPaths = [NSMutableSet setWithArray:[self.collectionView indexPathsForVisibleSupplementaryElementsOfKind:UICollectionElementKindSectionHeader]];
[visiblesSectionIndexPaths addObjectsFromArray:[self.collectionView indexPathsForVisibleSupplementaryElementsOfKind:UICollectionElementKindSectionFooter]];
// Find the section containing the gesture location
// Custom method that return the index of the section containing the given point, -1 if no section were found
NSInteger sectionIndex = [self sectionIndexContainingPoint:longPressLocation];
if (sectionIndex > -1) {
// If so, generate a virtual indexPath, where we can drop this item
// The management of the indexPath in the dataSource will happen in the moveItemAtIndexPath callback
// We return a new indexPath
int itemIndex = [self.collectionView numberOfItemsInSection:sectionIndex];
if (itemIndex<0) {
itemIndex=0;
}
// Generate a virtual indexPath
NSIndexPath * virtualIndexPath;
virtualIndexPath = [NSIndexPath indexPathForItem:itemIndex inSection:sectionIndex];
NSLog(@"virtual idxPath : %li - %li", virtualIndexPath.section, virtualIndexPath.item);
return virtualIndexPath;
}
return proposedIndexPath;
}
}
moveItemAtIndexPath
方法您必须在备份数据容器中更改项目所属的部分,并调用reloadData
更新您的collectionView。这部分完全依赖于你的dataSource,我自己用CoreData和NSFetchedResultsController完成了。
希望这会有所帮助。
N.B。:这是从我自己的实现中提取的大部分代码,我不确定它是否会按原样运行,但是你有关键可以使它滚动。