Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Move cells of UICollectionView in iOS?

I have a UICollectionView.I am trying to give it as SpringBoard functionality.I have am able to give the shake animation to each cell.But i want when icons are shaking then i should be able to move them as well.

For shaking the cells i have added the UILongPressGesture on each cell.When gesture end then i have added one custom animation on them & also added a delete button on top left corner.

Code for long press gesture:

declaration of variables

CGPoint p;
UILongPressGestureRecognizer *lpgr;
NSIndexPath *gesture_indexPath;

add gesture to collection view

 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;

Callback method

-(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];

    }

}

Cell for Item at inde path

- (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;
}

It works fine till here.

For moving the cell i made a sample app in which i added UICollectionViewController & override this method

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

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

This also works fine.It also uses long press gesture & when gesture detetced then i am able to move the cells.But now the issue is at one either i can move cell or animate them.If i add my custom gesture then i am not able to move the images.Please tell me how can i remove this issue?

like image 930
TechChain Avatar asked Nov 09 '15 09:11

TechChain


People also ask

What is the difference between Uitableview and UICollectionView?

Tableiw is a simple list, which displays single-dimensional rows of data. It's smooth because of cell reuse and other magic. 2. UICollectionView is the model for displaying multidimensional data .

What is UICollectionView flow layout?

Overview. A flow layout is a type of collection view layout. Items in the collection view flow from one row or column (depending on the scrolling direction) to the next, with each row containing as many cells as will fit. Cells can be the same sizes or different sizes.


1 Answers

Take a look at this project called DragDropCollectionView. It implements the dragging and dropping as well as the animation.

Edit: This problem can be broken down into 2 smaller subproblems:

  1. How to animate the cells
  2. How to reorder the cells using the drag and drop.

You should combine these 2 solutions into a subclas of UICollectionView to obtain your main solution.

How to animate the cells

To get the wiggle animation you need add 2 different animation effects:

  1. Move the cells vertically i.e up and down bounce
  2. Rotate the cells
  3. Lastly, add a random interval time for each cell so it does appear that the cells don't animate uniformly

Here is the code:

@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;
}

How to reorder the cells using drag and drop

So the idea is this: You’re not moving the actual cell around, but rather moving a UIImageView with a UIImage of the contents of the cell.

The algorithm goes more or less like this. I’ve broken it down into 3 sections, gestureRecognizerBegan, Changed and Ended

gestureRecognizerBegan:

  1. When the gestureRecognizer begins, determine if the long press was indeed on a cell (and not on empty space)
  2. Get a UIImage of the cell (See my method “getRasterizedImageOfCell”)
  3. Hide the cell (i.e alpha = 0), create a UIImageView with the exact frame of the cell so the user does not realize you’ve actually hidden the cell and you’re actually using the imageview.

gestureRecognizerChanged:

  1. Update the center of the UIImageView so that it moves with your finger.
  2. If the user has stopped moving his finge i.e. he is hovering over the cell he wants to replace, you now need to swap the cells. (Look at my function “shouldSwapCells”, this method returns a bool of whether the cells should swap or not)
  3. Move the cell that you were dragging to the new indexPath. (Look at my method “swapDraggedCell”). UICollectionView has a built-in method called "moveItemAtIndexPath: toIndexPath”, I’m not sure if UITableView has the same thing

gestureRecognizerEnd:

  1. “Drop” the UIImageView back onto the cell
  2. change the cell alpha from 0.0 to 1.0 and remove the UIImageView from the view.

Here is the code:

@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;
}

Hope this helps :)

like image 61
Lneuner Avatar answered Sep 19 '22 08:09

Lneuner