UICollectionView 在 UILongpress 手势下滚动
UICollectionView scroll when in UILongpress gesture
更新问题。当视图滚动时,我很难重新排序集合视图中的项目。这样做的原因是用于重新排序项目的代码在 UILongpress 手势的 UIGestureRecognizerStateChanged 中。因此,当视图滚动并且用户的手指在屏幕边缘静止时,不会调用 UILongpress 手势中的重新排序代码。如果我只是简单地插入重新订购代码:
// Is destination valid and is it different from source?
if (indexPath && ![indexPath isEqual:sourceIndexPath]) {
// ... update data source.
[self.imageArray exchangeObjectAtIndex:indexPath.row withObjectAtIndex:sourceIndexPath.row];
// ... move the rows.
[self.collectionView moveItemAtIndexPath:sourceIndexPath toIndexPath:indexPath];
// ... and update source so it is in sync with UI changes.
sourceIndexPath = indexPath;
进入 autoscrollTimerFired 方法后,项目的索引路径将不再正确,因为集合视图已滚动到用户静态手指下的新位置。
完整代码如下。有什么建议吗?
- (void)longPressGestureRecognized:(id)sender {
UILongPressGestureRecognizer *longPress = (UILongPressGestureRecognizer *)sender;
UIGestureRecognizerState state = longPress.state;
CGPoint location = [longPress locationInView:self.collectionView];
NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:location];
static UIView *snapshot = nil; ///< A snapshot of the row user is moving.
static NSIndexPath *sourceIndexPath = nil; ///< Initial index path, where gesture begins.
switch (state) {
case UIGestureRecognizerStateBegan: {
if (indexPath) {
[self.deleteButton removeFromSuperview];
self.isDeleteActive = NO;
sourceIndexPath = indexPath;
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath];
// Take a snapshot of the selected row using helper method.
snapshot = [self customSnapshoFromView:cell];
// Add the snapshot as subview, centered at cell's center...
__block CGPoint center = cell.center;
snapshot.center = center;
snapshot.alpha = 0.0;
[self.collectionView addSubview:snapshot];
[UIView animateWithDuration:0.25 animations:^{
// Offset for gesture location.
center.x = location.x;
snapshot.center = center;
snapshot.transform = CGAffineTransformMakeScale(1.05, 1.05);
snapshot.alpha = 0.98;
cell.alpha = 0.0;
} completion:^(BOOL finished) {
cell.hidden = YES;
}];
}
break;
}
case UIGestureRecognizerStateChanged: {
CGPoint center = snapshot.center;
center.x = location.x;
snapshot.center = center;
[self maybeAutoscrollForSnapshot:snapshot];
// Is destination valid and is it different from source?
if (indexPath && ![indexPath isEqual:sourceIndexPath]) {
// ... update data source.
[self.imageArray exchangeObjectAtIndex:indexPath.row withObjectAtIndex:sourceIndexPath.row];
// ... move the rows.
[self.collectionView moveItemAtIndexPath:sourceIndexPath toIndexPath:indexPath];
// ... and update source so it is in sync with UI changes.
sourceIndexPath = indexPath;
}
break;
}
default: {
// Clean up.
if(_autoscrollTimer)
{
[_autoscrollTimer invalidate]; _autoscrollTimer = nil;
}
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:sourceIndexPath];
cell.hidden = NO;
cell.alpha = 0.0;
[UIView animateWithDuration:0.25 animations:^{
snapshot.center = cell.center;
snapshot.transform = CGAffineTransformIdentity;
snapshot.alpha = 0.0;
cell.alpha = 1.0;
} completion:^(BOOL finished) {
sourceIndexPath = nil;
[snapshot removeFromSuperview];
snapshot = nil;
}];
break;
}
}
}
- (void)maybeAutoscrollForSnapshot:(UIImageView *)snapshot
{
_autoscrollDistance = 0;
if (CGRectGetMaxX(snapshot.frame) < self.collectionView.collectionViewLayout.collectionViewContentSize.width)
{
// only autoscroll if the content is larger than the view
if (self.collectionView.collectionViewLayout.collectionViewContentSize.width > self.view.frame.size.width)
{
float distanceFromTop = snapshot.center.x - CGRectGetMinX(self.collectionView.bounds);
float distanceFromBottom = CGRectGetMaxX(self.collectionView.bounds) - snapshot.center.x;
//NSLog(@"dist from left %f", distanceFromTop);
if (distanceFromTop < kAutoScrollingThreshold) {
_autoscrollDistance = [self autoscrollDistanceForProximityToEdge:distanceFromTop] * -1; // if scrolling up distance is negative
//NSLog(@"left dist %f", _autoscrollDistance);
} else if (distanceFromBottom < kAutoScrollingThreshold) {
_autoscrollDistance = [self autoscrollDistanceForProximityToEdge:distanceFromBottom];
//NSLog(@"right dist %f", _autoscrollDistance);
}
}
}
// if no autoscrolling, stop and clear timer
if (_autoscrollDistance == 0) {
[_autoscrollTimer invalidate];
_autoscrollTimer = nil;
}
// otherwise create and start timer (if we don't already have a timer going)
else if (_autoscrollTimer == nil) {
_autoscrollTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 60.0)
target:self
selector:@selector(autoscrollTimerFired:)
userInfo:snapshot
repeats:YES];
}
}
- (float)autoscrollDistanceForProximityToEdge:(float)proximity {
// the scroll distance grows as the proximity to the edge decreases, so that moving the thumb
// further over results in faster scrolling.
return ceilf((kAutoScrollingThreshold - proximity) / 5.0);
}
- (void)legalizeAutoscrollDistance {
// makes sure the autoscroll distance won't result in scrolling past the content of the scroll view
float minimumLegalDistance = (self.collectionView.contentOffset.x + self.collectionView.contentInset.left) * -1;
float maximumLegalDistance = self.collectionView.collectionViewLayout.collectionViewContentSize.width - (self.view.frame.size.width + self.collectionView.contentOffset.x);
//NSLog(@"min dist %f", minimumLegalDistance);
//NSLog(@"max dist %f", maximumLegalDistance);
_autoscrollDistance = MAX(_autoscrollDistance, minimumLegalDistance);
_autoscrollDistance = MIN(_autoscrollDistance, maximumLegalDistance);
//NSLog(@"autoscroll distance %f", _autoscrollDistance);
}
- (void)autoscrollTimerFired:(NSTimer*)timer {
// NSLog(@"autoscrolling: %.2f",_autoscrollDistance);
[self legalizeAutoscrollDistance];
// autoscroll by changing content offset
CGPoint contentOffset = [self.collectionView contentOffset];
contentOffset.x += _autoscrollDistance;
//NSLog(@"%f",contentOffset.x);
[self.collectionView setContentOffset:contentOffset];
// adjust thumb position so it appears to stay still
UIImageView *snapshot = (UIImageView *)[timer userInfo];
snapshot.center = CGPointMake(snapshot.center.x + _autoscrollDistance, snapshot.center.y);
}
在调用 maybeAutoscrollForSnapshot 函数时将发送者 (UILongPressGestureRecognizer
) 保存在 UIGestureRecognizerStateChanged
中,然后让计时器使用您保存的手势识别器对象调用 longPressGestureRecognized
。手势识别器相对于 UIWindow 的位置相同,但是当您计算 indexPath
它应该是正确的,因为 scrollView 有滚动条。
附带说明一下,当您到达边缘时,您真的需要让视图滚动吗?
我实现了同样的事情,但没有自动滚动部分,它工作正常。
您的列表是否很长,如果自动滚动会有帮助?
更新问题。当视图滚动时,我很难重新排序集合视图中的项目。这样做的原因是用于重新排序项目的代码在 UILongpress 手势的 UIGestureRecognizerStateChanged 中。因此,当视图滚动并且用户的手指在屏幕边缘静止时,不会调用 UILongpress 手势中的重新排序代码。如果我只是简单地插入重新订购代码:
// Is destination valid and is it different from source?
if (indexPath && ![indexPath isEqual:sourceIndexPath]) {
// ... update data source.
[self.imageArray exchangeObjectAtIndex:indexPath.row withObjectAtIndex:sourceIndexPath.row];
// ... move the rows.
[self.collectionView moveItemAtIndexPath:sourceIndexPath toIndexPath:indexPath];
// ... and update source so it is in sync with UI changes.
sourceIndexPath = indexPath;
进入 autoscrollTimerFired 方法后,项目的索引路径将不再正确,因为集合视图已滚动到用户静态手指下的新位置。
完整代码如下。有什么建议吗?
- (void)longPressGestureRecognized:(id)sender {
UILongPressGestureRecognizer *longPress = (UILongPressGestureRecognizer *)sender;
UIGestureRecognizerState state = longPress.state;
CGPoint location = [longPress locationInView:self.collectionView];
NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:location];
static UIView *snapshot = nil; ///< A snapshot of the row user is moving.
static NSIndexPath *sourceIndexPath = nil; ///< Initial index path, where gesture begins.
switch (state) {
case UIGestureRecognizerStateBegan: {
if (indexPath) {
[self.deleteButton removeFromSuperview];
self.isDeleteActive = NO;
sourceIndexPath = indexPath;
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath];
// Take a snapshot of the selected row using helper method.
snapshot = [self customSnapshoFromView:cell];
// Add the snapshot as subview, centered at cell's center...
__block CGPoint center = cell.center;
snapshot.center = center;
snapshot.alpha = 0.0;
[self.collectionView addSubview:snapshot];
[UIView animateWithDuration:0.25 animations:^{
// Offset for gesture location.
center.x = location.x;
snapshot.center = center;
snapshot.transform = CGAffineTransformMakeScale(1.05, 1.05);
snapshot.alpha = 0.98;
cell.alpha = 0.0;
} completion:^(BOOL finished) {
cell.hidden = YES;
}];
}
break;
}
case UIGestureRecognizerStateChanged: {
CGPoint center = snapshot.center;
center.x = location.x;
snapshot.center = center;
[self maybeAutoscrollForSnapshot:snapshot];
// Is destination valid and is it different from source?
if (indexPath && ![indexPath isEqual:sourceIndexPath]) {
// ... update data source.
[self.imageArray exchangeObjectAtIndex:indexPath.row withObjectAtIndex:sourceIndexPath.row];
// ... move the rows.
[self.collectionView moveItemAtIndexPath:sourceIndexPath toIndexPath:indexPath];
// ... and update source so it is in sync with UI changes.
sourceIndexPath = indexPath;
}
break;
}
default: {
// Clean up.
if(_autoscrollTimer)
{
[_autoscrollTimer invalidate]; _autoscrollTimer = nil;
}
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:sourceIndexPath];
cell.hidden = NO;
cell.alpha = 0.0;
[UIView animateWithDuration:0.25 animations:^{
snapshot.center = cell.center;
snapshot.transform = CGAffineTransformIdentity;
snapshot.alpha = 0.0;
cell.alpha = 1.0;
} completion:^(BOOL finished) {
sourceIndexPath = nil;
[snapshot removeFromSuperview];
snapshot = nil;
}];
break;
}
}
}
- (void)maybeAutoscrollForSnapshot:(UIImageView *)snapshot
{
_autoscrollDistance = 0;
if (CGRectGetMaxX(snapshot.frame) < self.collectionView.collectionViewLayout.collectionViewContentSize.width)
{
// only autoscroll if the content is larger than the view
if (self.collectionView.collectionViewLayout.collectionViewContentSize.width > self.view.frame.size.width)
{
float distanceFromTop = snapshot.center.x - CGRectGetMinX(self.collectionView.bounds);
float distanceFromBottom = CGRectGetMaxX(self.collectionView.bounds) - snapshot.center.x;
//NSLog(@"dist from left %f", distanceFromTop);
if (distanceFromTop < kAutoScrollingThreshold) {
_autoscrollDistance = [self autoscrollDistanceForProximityToEdge:distanceFromTop] * -1; // if scrolling up distance is negative
//NSLog(@"left dist %f", _autoscrollDistance);
} else if (distanceFromBottom < kAutoScrollingThreshold) {
_autoscrollDistance = [self autoscrollDistanceForProximityToEdge:distanceFromBottom];
//NSLog(@"right dist %f", _autoscrollDistance);
}
}
}
// if no autoscrolling, stop and clear timer
if (_autoscrollDistance == 0) {
[_autoscrollTimer invalidate];
_autoscrollTimer = nil;
}
// otherwise create and start timer (if we don't already have a timer going)
else if (_autoscrollTimer == nil) {
_autoscrollTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 60.0)
target:self
selector:@selector(autoscrollTimerFired:)
userInfo:snapshot
repeats:YES];
}
}
- (float)autoscrollDistanceForProximityToEdge:(float)proximity {
// the scroll distance grows as the proximity to the edge decreases, so that moving the thumb
// further over results in faster scrolling.
return ceilf((kAutoScrollingThreshold - proximity) / 5.0);
}
- (void)legalizeAutoscrollDistance {
// makes sure the autoscroll distance won't result in scrolling past the content of the scroll view
float minimumLegalDistance = (self.collectionView.contentOffset.x + self.collectionView.contentInset.left) * -1;
float maximumLegalDistance = self.collectionView.collectionViewLayout.collectionViewContentSize.width - (self.view.frame.size.width + self.collectionView.contentOffset.x);
//NSLog(@"min dist %f", minimumLegalDistance);
//NSLog(@"max dist %f", maximumLegalDistance);
_autoscrollDistance = MAX(_autoscrollDistance, minimumLegalDistance);
_autoscrollDistance = MIN(_autoscrollDistance, maximumLegalDistance);
//NSLog(@"autoscroll distance %f", _autoscrollDistance);
}
- (void)autoscrollTimerFired:(NSTimer*)timer {
// NSLog(@"autoscrolling: %.2f",_autoscrollDistance);
[self legalizeAutoscrollDistance];
// autoscroll by changing content offset
CGPoint contentOffset = [self.collectionView contentOffset];
contentOffset.x += _autoscrollDistance;
//NSLog(@"%f",contentOffset.x);
[self.collectionView setContentOffset:contentOffset];
// adjust thumb position so it appears to stay still
UIImageView *snapshot = (UIImageView *)[timer userInfo];
snapshot.center = CGPointMake(snapshot.center.x + _autoscrollDistance, snapshot.center.y);
}
在调用 maybeAutoscrollForSnapshot 函数时将发送者 (UILongPressGestureRecognizer
) 保存在 UIGestureRecognizerStateChanged
中,然后让计时器使用您保存的手势识别器对象调用 longPressGestureRecognized
。手势识别器相对于 UIWindow 的位置相同,但是当您计算 indexPath
它应该是正确的,因为 scrollView 有滚动条。
附带说明一下,当您到达边缘时,您真的需要让视图滚动吗?
我实现了同样的事情,但没有自动滚动部分,它工作正常。
您的列表是否很长,如果自动滚动会有帮助?