Переместить ячейки UICollectionView в iOS?

У меня есть UICollectionView. Я пытаюсь дать его как функцию SpringBoard. Я могу дать анимацию shake для каждой ячейки. Но я хочу, когда значки дрожат, тогда я также смогу их перемещать.

Для встряхивания ячеек я добавил UILongPressGesture в каждую ячейку. Когда конец жестов, я добавил одну пользовательскую анимацию, а также добавил кнопку удаления в верхнем левом углу.

Код для длинного жестов нажатия:

declaration of variables

CGPoint p;
UILongPressGestureRecognizer *lpgr;
NSIndexPath *gesture_indexPath;

добавить жест в представление коллекции

 lpgr
    = [[UILongPressGestureRecognizer alloc]
       initWithTarget:self action:@selector(handleLongPress:)];
        lpgr.minimumPressDuration = .3; // To detect after how many seconds you want shake the cells
        lpgr.delegate = self;
        [self.collection_view addGestureRecognizer:lpgr];

    lpgr.delaysTouchesBegan = YES;

Метод обратного вызова

-(void)handleLongPress:(UILongPressGestureRecognizer *)gestureRecognizer
{
    if (gestureRecognizer.state != UIGestureRecognizerStateEnded)
    {
        return;
    }
    p = [gestureRecognizer locationInView:self.collection_view];

    NSIndexPath *indexPath = [self.collection_view indexPathForItemAtPoint:p];
    if (indexPath == nil)
    {
        NSLog(@"couldn't find index path");
    }
    else
    {
        [[NSUserDefaults standardUserDefaults]setValue:@"yes" forKey:@"longPressed"];
        [self.collection_view reloadData];

    }

}

Ячейка для элемента по пути inde

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"arr_album index row");
    BlogAlbumCell  *cell;
    static NSString *identifier = @"UserBlogAlbum";
    cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
    UserAlbum *user_allbum=[arr_userAlbums objectAtIndex:indexPath.row];
    cell.label_blog_name.text=user_allbum.album_name;
    cell.image_blog_image.image = [UIImage imageNamed:@"more.png"];
    [cell.image_blog_image setImageWithURL:[NSURL URLWithString:[IMAGE_BASE_URL stringByAppendingString:user_allbum.album_image]]];
    if([[[NSUserDefaults standardUserDefaults]valueForKey:@"longPressed"] isEqualToString:@"yes"])
    {
        CABasicAnimation* anim = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
        [anim setToValue:[NSNumber numberWithFloat:0.0f]];
        [anim setFromValue:[NSNumber numberWithDouble:M_PI/50]];
        [anim setDuration:0.1];
        [anim setRepeatCount:NSUIntegerMax];
        [anim setAutoreverses:YES];
        cell.layer.shouldRasterize = YES;
        [cell.layer addAnimation:anim forKey:@"SpringboardShake"];
        CGFloat delButtonSize = 20;

        UIButton *delButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, delButtonSize, delButtonSize)];
        delButton.center = CGPointMake(9, 10);
        delButton.backgroundColor = [UIColor clearColor];
        [delButton setImage: [UIImage imageNamed:@"cross_30.png"] forState:UIControlStateNormal];
        [cell addSubview:delButton];
        [delButton addTarget:self action:@selector(deleteRecipe:) forControlEvents:UIControlEventTouchUpInside];
    }
    else if ([[[NSUserDefaults standardUserDefaults]valueForKey:@"singleTap"] isEqualToString:@"yes"])
    { 
        for(UIView *subview in [cell subviews])
        {
            if([subview isKindOfClass:[UIButton class]])
            {
                [subview removeFromSuperview];
            }
            else
            {
                // Do nothing - not a UIButton or subclass instance
            }
        }
        [cell.layer removeAllAnimations];
        // _deleteButton.hidden = YES; 
        // [_deleteButton removeFromSuperview];
    }
        return cell;
}

Он отлично работает до сих пор.

Для перемещения ячейки я сделал пример приложения, в котором я добавил UICollectionViewController и переопределить этот метод

-(void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
{

    NSLog(@"Move at index path called");
}

Это также отлично работает. Он также использует длинный жест нажатия, и когда жест ограничен, тогда я могу перемещать ячейки. Но теперь проблема в одном либо я могу переместить ячейку, либо оживить их. Если я добавлю свой собственный жест, Я не могу переместить изображения. Пожалуйста, скажите мне, как я могу удалить эту проблему?

Ответ 1

Взгляните на этот проект под названием DragDropCollectionView. Он реализует перетаскивание, а также анимацию.

Изменить: эту проблему можно разбить на две более мелкие подзадачи:

  • Как оживить ячейки
  • Как изменить порядок ячеек с помощью перетаскивания.

Вы должны объединить эти 2 решения в подклассу UICollectionView, чтобы получить основное решение.

Как оживить ячейки

Чтобы получить анимацию вибрирования, вам нужно добавить 2 разных анимационных эффекта:

  • Переместить ячейки по вертикали i.e вверх и вниз bounce
  • Поверните ячейки
  • Наконец, добавьте случайное интервальное время для каждой ячейки, чтобы оно выглядело так, что ячейки не анимируются равномерно

Вот код:

@interface DragDropCollectionView ()
@property (assign, nonatomic) BOOL isWiggling;
@end

@implementation DragDropCollectionView

//Start and Stop methods for wiggle
- (void) startWiggle {
    for (UICollectionViewCell *cell in self.visibleCells) {
        [self addWiggleAnimationToCell:cell];
    }
    self.isWiggling = true;
}

- (void)stopWiggle {
    for (UICollectionViewCell *cell in self.visibleCells) {
        [cell.layer removeAllAnimations];
    }
    self.isWiggling = false;
}

//- (UICollectionViewCell *)dequ

- (UICollectionViewCell *)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(nonnull NSIndexPath *)indexPath{
    UICollectionViewCell *cell = [super dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
    if (self.isWiggling) {
        [self addWiggleAnimationToCell:cell];
    } else {
        [cell.layer removeAllAnimations];
    }
    return [[UICollectionViewCell alloc] init];
}

//Animations
- (void)addWiggleAnimationToCell:(UICollectionViewCell *)cell {
    [CATransaction begin];
    [CATransaction setDisableActions:false];
    [cell.layer addAnimation:[self rotationAnimation] forKey:@"rotation"];
    [cell.layer addAnimation:[self bounceAnimation] forKey:@"bounce"];
    [CATransaction commit];

}

- (CAKeyframeAnimation *)rotationAnimation {
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation.z"];
    CGFloat angle = 0.04;
    NSTimeInterval duration = 0.1;
    double variance = 0.025;
    animation.values = @[@(angle), @(-1 * angle)];
    animation.autoreverses = YES;
    animation.duration = [self randomizeInterval:duration withVariance: variance];
    animation.repeatCount = INFINITY;
    return animation;
}

- (CAKeyframeAnimation *)bounceAnimation {
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.translation.y"];
    CGFloat bounce = 3.0;
    NSTimeInterval duration = 0.12;
    double variance = 0.025;
    animation.values = @[@(bounce), @(-1 * bounce)];
    animation.autoreverses = YES;
    animation.duration = [self randomizeInterval:duration withVariance: variance];
    animation.repeatCount = INFINITY;

    return animation;
}

- (NSTimeInterval)randomizeInterval:(NSTimeInterval)interval withVariance:(double)variance {
    double randomDecimal = (arc4random() % 1000 - 500.0) / 500.0;
    return interval + variance * randomDecimal;
}

Как изменить порядок ячеек с помощью перетаскивания

Итак, идея такова: вы не перемещаете фактическую ячейку вокруг, а скорее перемещаете UIImageView с UIImage содержимого ячейки.

Алгоритм более или менее подобен этому. Ive разбил его на 3 раздела, gestureRecognizerBegan, Changed and Ended

gestureRecognizerBegan:

  • Когда начнется распознавание gestureRecognizer, определите, было ли длительное нажатие действительно на ячейке (а не на пустом пространстве).
  • Получить UIImage ячейки (см. мой метод "getRasterizedImageOfCell" )
  • Скрыть ячейку (i.e alpha = 0), создать UIImageView с точным фреймом ячейки, чтобы пользователь не понимал, что вы действительно скрыли ячейку, а вы действительно используете изображение.

gestureRecognizerChanged:

  • Обновите центр UIImageView, чтобы он перемещался пальцем.
  • Если пользователь перестает двигать пальцами, то есть он нависает над ячейкой, которую он хочет заменить, теперь вам нужно поменять ячейки. (Посмотрите на мою функцию "shouldSwapCells", этот метод возвращает bool того, должны ли ячейки меняться или нет)
  • Переместите ячейку, которую вы перетаскиваете, в новую indexPath. (Посмотрите на мой метод "swapDraggedCell" ). UICollectionView имеет встроенный метод "moveItemAtIndexPath: toIndexPath", Im не уверен, что UITableView имеет то же самое.

gestureRecognizerEnd:

  • "Отбросьте UIImageView обратно в ячейку
  • измените ячейку alpha с 0.0 на 1.0 и удалите UIImageView из представления.

Вот код:

@interface DragDropCollectionView ()
@property (strong, nonatomic) NSIndexPath *draggedCellIndexPath;
@property (strong, nonatomic) UIImageView *draggingImageView;
@property (assign, nonatomic) CGPoint touchOffsetFromCenterOfCell;
@property (strong, nonatomic) UILongPressGestureRecognizer *longPressRecognizer;
@end

@implementation DragDropCollectionView

- (void)handleLongPress:(UILongPressGestureRecognizer *)longPressRecognizer {
    CGPoint touchLocation = [longPressRecognizer locationInView:self];
    switch (longPressRecognizer.state) {
        case UIGestureRecognizerStateBegan: {
            self.draggedCellIndexPath = [self indexPathForItemAtPoint:touchLocation];
            if (self.draggedCellIndexPath != nil) {
                UICollectionViewCell *draggedCell = [self cellForItemAtIndexPath:self.draggedCellIndexPath];
                self.draggingImageView = [[UIImageView alloc] initWithImage:[self rasterizedImageCopyOfCell:draggedCell]];
                self.draggingImageView.center = draggedCell.center;
                [self addSubview:self.draggingImageView];
                draggedCell.alpha = 0.0;
                self.touchOffsetFromCenterOfCell = CGPointMake(draggedCell.center.x - touchLocation.x, draggedCell.center.y - touchLocation.y);
                [UIView animateWithDuration:0.4 animations:^{
                    self.draggingImageView.transform = CGAffineTransformMakeScale(1.3, 1.3);
                    self.draggingImageView.alpha = 0.8;
                }];
            }
            break;
        }
        case UIGestureRecognizerStateChanged: {
            if (self.draggedCellIndexPath != nil) {
                self.draggingImageView.center = CGPointMake(touchLocation.x + self.touchOffsetFromCenterOfCell.x, touchLocation.y + self.touchOffsetFromCenterOfCell.y);
            }
            float pingInterval = 0.3;
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(pingInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSIndexPath *newIndexPath = [self indexPathToSwapCellWithAtPreviousTouchLocation:touchLocation];
                if (newIndexPath) {
                    [self swapDraggedCellWithCellAtIndexPath:newIndexPath];
                }
            });
            break;
        }
        case UIGestureRecognizerStateEnded: {
            if (self.draggedCellIndexPath != nil ) {
                UICollectionViewCell *draggedCell = [self cellForItemAtIndexPath:self.draggedCellIndexPath];
                [UIView animateWithDuration:0.4 animations:^{
                    self.draggingImageView.transform = CGAffineTransformIdentity;
                    self.draggingImageView.alpha = 1.0;
                    if (draggedCell != nil) {
                        self.draggingImageView.center = draggedCell.center;
                    }
                } completion:^(BOOL finished) {
                    [self.draggingImageView removeFromSuperview];
                    self.draggingImageView = nil;
                    if (draggedCell != nil) {
                        draggedCell.alpha = 1.0;
                        self.draggedCellIndexPath = nil;
                    }
                }];
            }
        }

        default:
            break;
    }
}

- (UIImage *)rasterizedImageCopyOfCell:(UICollectionViewCell *)cell {
    UIGraphicsBeginImageContextWithOptions(cell.bounds.size, false, 0.0);
    [cell.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    return image;
}

- (NSIndexPath *)indexPathToSwapCellWithAtPreviousTouchLocation:(CGPoint)previousTouchLocation {
    CGPoint currentTouchLocation = [self.longPressRecognizer locationInView:self];
    if (!isnan(currentTouchLocation.x) && !isnan(currentTouchLocation.y)) {
        if ([self distanceBetweenPoints:currentTouchLocation secondPoint:previousTouchLocation] < 20.0) {
            NSIndexPath *newIndexPath = [self indexPathForItemAtPoint:currentTouchLocation];
            return newIndexPath;
        }
    }
    return nil;
}

- (CGFloat)distanceBetweenPoints:(CGPoint)firstPoint secondPoint:(CGPoint)secondPoint {
    CGFloat xDistance = firstPoint.x - secondPoint.x;
    CGFloat yDistance = firstPoint.y - secondPoint.y;
    return sqrtf(xDistance * xDistance + yDistance * yDistance);
}

- (void)swapDraggedCellWithCellAtIndexPath:(NSIndexPath *)newIndexPath {
    [self moveItemAtIndexPath:self.draggedCellIndexPath toIndexPath:newIndexPath];
    UICollectionViewCell *draggedCell = [self cellForItemAtIndexPath:newIndexPath];
    draggedCell.alpha = 0.0;
    self.draggedCellIndexPath = newIndexPath;
}

Надеюсь, что это поможет:)